commit 0a319d80d87ea42b2470c800250bc23e7bf68223 Author: Xpl0itU <24777100+Xpl0itU@users.noreply.github.com> Date: Tue Jul 18 12:27:23 2023 +0200 Unfinished work diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f749b3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.d +*.o +*.so +*.dll +gtitles.c diff --git a/cdecrypt/aes.c b/cdecrypt/aes.c new file mode 100644 index 0000000..44d229d --- /dev/null +++ b/cdecrypt/aes.c @@ -0,0 +1,869 @@ +/* + * FIPS-197 compliant AES implementation + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) and was downloaded from + * https://github.com/Secure-Embedded-Systems/RSA-example/tree/master/library/. + */ +/* + * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. + * + * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + */ + +#include "aes.h" + +#include + +/* Implementation that should never be optimized out by the compiler */ +static void zeroize(void *v, size_t n) { + volatile uint8_t *p = (uint8_t *)v; + while (n--) + *p++ = 0; +} + +/* + * 32-bit integer manipulation macros (little endian) + */ +#ifndef GET_UINT32_LE +#define GET_UINT32_LE(n, b, i) \ + { \ + (n) = ((uint32_t)(b)[(i)]) | ((uint32_t)(b)[(i) + 1] << 8) | \ + ((uint32_t)(b)[(i) + 2] << 16) | ((uint32_t)(b)[(i) + 3] << 24); \ + } +#endif + +#ifndef PUT_UINT32_LE +#define PUT_UINT32_LE(n, b, i) \ + { \ + (b)[(i)] = (uint8_t)(((n)) & 0xFF); \ + (b)[(i) + 1] = (uint8_t)(((n) >> 8) & 0xFF); \ + (b)[(i) + 2] = (uint8_t)(((n) >> 16) & 0xFF); \ + (b)[(i) + 3] = (uint8_t)(((n) >> 24) & 0xFF); \ + } +#endif + +#if defined(AES_ROM_TABLES) +/* + * Forward S-box + */ +static const uint8_t FSb[256] = { + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, + 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, + 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, + 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, + 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, + 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, + 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, + 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, + 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, + 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, + 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, + 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, + 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, + 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, + 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, + 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, + 0xB0, 0x54, 0xBB, 0x16}; + +/* + * Forward tables + */ +#define FT \ + \ + V(A5, 63, 63, C6), V(84, 7C, 7C, F8), V(99, 77, 77, EE), V(8D, 7B, 7B, F6), \ + V(0D, F2, F2, FF), V(BD, 6B, 6B, D6), V(B1, 6F, 6F, DE), \ + V(54, C5, C5, 91), V(50, 30, 30, 60), V(03, 01, 01, 02), \ + V(A9, 67, 67, CE), V(7D, 2B, 2B, 56), V(19, FE, FE, E7), \ + V(62, D7, D7, B5), V(E6, AB, AB, 4D), V(9A, 76, 76, EC), \ + V(45, CA, CA, 8F), V(9D, 82, 82, 1F), V(40, C9, C9, 89), \ + V(87, 7D, 7D, FA), V(15, FA, FA, EF), V(EB, 59, 59, B2), \ + V(C9, 47, 47, 8E), V(0B, F0, F0, FB), V(EC, AD, AD, 41), \ + V(67, D4, D4, B3), V(FD, A2, A2, 5F), V(EA, AF, AF, 45), \ + V(BF, 9C, 9C, 23), V(F7, A4, A4, 53), V(96, 72, 72, E4), \ + V(5B, C0, C0, 9B), V(C2, B7, B7, 75), V(1C, FD, FD, E1), \ + V(AE, 93, 93, 3D), V(6A, 26, 26, 4C), V(5A, 36, 36, 6C), \ + V(41, 3F, 3F, 7E), V(02, F7, F7, F5), V(4F, CC, CC, 83), \ + V(5C, 34, 34, 68), V(F4, A5, A5, 51), V(34, E5, E5, D1), \ + V(08, F1, F1, F9), V(93, 71, 71, E2), V(73, D8, D8, AB), \ + V(53, 31, 31, 62), V(3F, 15, 15, 2A), V(0C, 04, 04, 08), \ + V(52, C7, C7, 95), V(65, 23, 23, 46), V(5E, C3, C3, 9D), \ + V(28, 18, 18, 30), V(A1, 96, 96, 37), V(0F, 05, 05, 0A), \ + V(B5, 9A, 9A, 2F), V(09, 07, 07, 0E), V(36, 12, 12, 24), \ + V(9B, 80, 80, 1B), V(3D, E2, E2, DF), V(26, EB, EB, CD), \ + V(69, 27, 27, 4E), V(CD, B2, B2, 7F), V(9F, 75, 75, EA), \ + V(1B, 09, 09, 12), V(9E, 83, 83, 1D), V(74, 2C, 2C, 58), \ + V(2E, 1A, 1A, 34), V(2D, 1B, 1B, 36), V(B2, 6E, 6E, DC), \ + V(EE, 5A, 5A, B4), V(FB, A0, A0, 5B), V(F6, 52, 52, A4), \ + V(4D, 3B, 3B, 76), V(61, D6, D6, B7), V(CE, B3, B3, 7D), \ + V(7B, 29, 29, 52), V(3E, E3, E3, DD), V(71, 2F, 2F, 5E), \ + V(97, 84, 84, 13), V(F5, 53, 53, A6), V(68, D1, D1, B9), \ + V(00, 00, 00, 00), V(2C, ED, ED, C1), V(60, 20, 20, 40), \ + V(1F, FC, FC, E3), V(C8, B1, B1, 79), V(ED, 5B, 5B, B6), \ + V(BE, 6A, 6A, D4), V(46, CB, CB, 8D), V(D9, BE, BE, 67), \ + V(4B, 39, 39, 72), V(DE, 4A, 4A, 94), V(D4, 4C, 4C, 98), \ + V(E8, 58, 58, B0), V(4A, CF, CF, 85), V(6B, D0, D0, BB), \ + V(2A, EF, EF, C5), V(E5, AA, AA, 4F), V(16, FB, FB, ED), \ + V(C5, 43, 43, 86), V(D7, 4D, 4D, 9A), V(55, 33, 33, 66), \ + V(94, 85, 85, 11), V(CF, 45, 45, 8A), V(10, F9, F9, E9), \ + V(06, 02, 02, 04), V(81, 7F, 7F, FE), V(F0, 50, 50, A0), \ + V(44, 3C, 3C, 78), V(BA, 9F, 9F, 25), V(E3, A8, A8, 4B), \ + V(F3, 51, 51, A2), V(FE, A3, A3, 5D), V(C0, 40, 40, 80), \ + V(8A, 8F, 8F, 05), V(AD, 92, 92, 3F), V(BC, 9D, 9D, 21), \ + V(48, 38, 38, 70), V(04, F5, F5, F1), V(DF, BC, BC, 63), \ + V(C1, B6, B6, 77), V(75, DA, DA, AF), V(63, 21, 21, 42), \ + V(30, 10, 10, 20), V(1A, FF, FF, E5), V(0E, F3, F3, FD), \ + V(6D, D2, D2, BF), V(4C, CD, CD, 81), V(14, 0C, 0C, 18), \ + V(35, 13, 13, 26), V(2F, EC, EC, C3), V(E1, 5F, 5F, BE), \ + V(A2, 97, 97, 35), V(CC, 44, 44, 88), V(39, 17, 17, 2E), \ + V(57, C4, C4, 93), V(F2, A7, A7, 55), V(82, 7E, 7E, FC), \ + V(47, 3D, 3D, 7A), V(AC, 64, 64, C8), V(E7, 5D, 5D, BA), \ + V(2B, 19, 19, 32), V(95, 73, 73, E6), V(A0, 60, 60, C0), \ + V(98, 81, 81, 19), V(D1, 4F, 4F, 9E), V(7F, DC, DC, A3), \ + V(66, 22, 22, 44), V(7E, 2A, 2A, 54), V(AB, 90, 90, 3B), \ + V(83, 88, 88, 0B), V(CA, 46, 46, 8C), V(29, EE, EE, C7), \ + V(D3, B8, B8, 6B), V(3C, 14, 14, 28), V(79, DE, DE, A7), \ + V(E2, 5E, 5E, BC), V(1D, 0B, 0B, 16), V(76, DB, DB, AD), \ + V(3B, E0, E0, DB), V(56, 32, 32, 64), V(4E, 3A, 3A, 74), \ + V(1E, 0A, 0A, 14), V(DB, 49, 49, 92), V(0A, 06, 06, 0C), \ + V(6C, 24, 24, 48), V(E4, 5C, 5C, B8), V(5D, C2, C2, 9F), \ + V(6E, D3, D3, BD), V(EF, AC, AC, 43), V(A6, 62, 62, C4), \ + V(A8, 91, 91, 39), V(A4, 95, 95, 31), V(37, E4, E4, D3), \ + V(8B, 79, 79, F2), V(32, E7, E7, D5), V(43, C8, C8, 8B), \ + V(59, 37, 37, 6E), V(B7, 6D, 6D, DA), V(8C, 8D, 8D, 01), \ + V(64, D5, D5, B1), V(D2, 4E, 4E, 9C), V(E0, A9, A9, 49), \ + V(B4, 6C, 6C, D8), V(FA, 56, 56, AC), V(07, F4, F4, F3), \ + V(25, EA, EA, CF), V(AF, 65, 65, CA), V(8E, 7A, 7A, F4), \ + V(E9, AE, AE, 47), V(18, 08, 08, 10), V(D5, BA, BA, 6F), \ + V(88, 78, 78, F0), V(6F, 25, 25, 4A), V(72, 2E, 2E, 5C), \ + V(24, 1C, 1C, 38), V(F1, A6, A6, 57), V(C7, B4, B4, 73), \ + V(51, C6, C6, 97), V(23, E8, E8, CB), V(7C, DD, DD, A1), \ + V(9C, 74, 74, E8), V(21, 1F, 1F, 3E), V(DD, 4B, 4B, 96), \ + V(DC, BD, BD, 61), V(86, 8B, 8B, 0D), V(85, 8A, 8A, 0F), \ + V(90, 70, 70, E0), V(42, 3E, 3E, 7C), V(C4, B5, B5, 71), \ + V(AA, 66, 66, CC), V(D8, 48, 48, 90), V(05, 03, 03, 06), \ + V(01, F6, F6, F7), V(12, 0E, 0E, 1C), V(A3, 61, 61, C2), \ + V(5F, 35, 35, 6A), V(F9, 57, 57, AE), V(D0, B9, B9, 69), \ + V(91, 86, 86, 17), V(58, C1, C1, 99), V(27, 1D, 1D, 3A), \ + V(B9, 9E, 9E, 27), V(38, E1, E1, D9), V(13, F8, F8, EB), \ + V(B3, 98, 98, 2B), V(33, 11, 11, 22), V(BB, 69, 69, D2), \ + V(70, D9, D9, A9), V(89, 8E, 8E, 07), V(A7, 94, 94, 33), \ + V(B6, 9B, 9B, 2D), V(22, 1E, 1E, 3C), V(92, 87, 87, 15), \ + V(20, E9, E9, C9), V(49, CE, CE, 87), V(FF, 55, 55, AA), \ + V(78, 28, 28, 50), V(7A, DF, DF, A5), V(8F, 8C, 8C, 03), \ + V(F8, A1, A1, 59), V(80, 89, 89, 09), V(17, 0D, 0D, 1A), \ + V(DA, BF, BF, 65), V(31, E6, E6, D7), V(C6, 42, 42, 84), \ + V(B8, 68, 68, D0), V(C3, 41, 41, 82), V(B0, 99, 99, 29), \ + V(77, 2D, 2D, 5A), V(11, 0F, 0F, 1E), V(CB, B0, B0, 7B), \ + V(FC, 54, 54, A8), V(D6, BB, BB, 6D), V(3A, 16, 16, 2C) + +#define V(a, b, c, d) 0x##a##b##c##d +static const uint32_t FT0[256] = {FT}; +#undef V + +#define V(a, b, c, d) 0x##b##c##d##a +static const uint32_t FT1[256] = {FT}; +#undef V + +#define V(a, b, c, d) 0x##c##d##a##b +static const uint32_t FT2[256] = {FT}; +#undef V + +#define V(a, b, c, d) 0x##d##a##b##c +static const uint32_t FT3[256] = {FT}; +#undef V + +#undef FT + +/* + * Reverse S-box + */ +static const uint8_t RSb[256] = { + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, + 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, + 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, + 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, + 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, + 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, + 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, + 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, + 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, + 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, + 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, + 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, + 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, + 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0C, 0x7D}; + +/* + * Reverse tables + */ +#define RT \ + \ + V(50, A7, F4, 51), V(53, 65, 41, 7E), V(C3, A4, 17, 1A), V(96, 5E, 27, 3A), \ + V(CB, 6B, AB, 3B), V(F1, 45, 9D, 1F), V(AB, 58, FA, AC), \ + V(93, 03, E3, 4B), V(55, FA, 30, 20), V(F6, 6D, 76, AD), \ + V(91, 76, CC, 88), V(25, 4C, 02, F5), V(FC, D7, E5, 4F), \ + V(D7, CB, 2A, C5), V(80, 44, 35, 26), V(8F, A3, 62, B5), \ + V(49, 5A, B1, DE), V(67, 1B, BA, 25), V(98, 0E, EA, 45), \ + V(E1, C0, FE, 5D), V(02, 75, 2F, C3), V(12, F0, 4C, 81), \ + V(A3, 97, 46, 8D), V(C6, F9, D3, 6B), V(E7, 5F, 8F, 03), \ + V(95, 9C, 92, 15), V(EB, 7A, 6D, BF), V(DA, 59, 52, 95), \ + V(2D, 83, BE, D4), V(D3, 21, 74, 58), V(29, 69, E0, 49), \ + V(44, C8, C9, 8E), V(6A, 89, C2, 75), V(78, 79, 8E, F4), \ + V(6B, 3E, 58, 99), V(DD, 71, B9, 27), V(B6, 4F, E1, BE), \ + V(17, AD, 88, F0), V(66, AC, 20, C9), V(B4, 3A, CE, 7D), \ + V(18, 4A, DF, 63), V(82, 31, 1A, E5), V(60, 33, 51, 97), \ + V(45, 7F, 53, 62), V(E0, 77, 64, B1), V(84, AE, 6B, BB), \ + V(1C, A0, 81, FE), V(94, 2B, 08, F9), V(58, 68, 48, 70), \ + V(19, FD, 45, 8F), V(87, 6C, DE, 94), V(B7, F8, 7B, 52), \ + V(23, D3, 73, AB), V(E2, 02, 4B, 72), V(57, 8F, 1F, E3), \ + V(2A, AB, 55, 66), V(07, 28, EB, B2), V(03, C2, B5, 2F), \ + V(9A, 7B, C5, 86), V(A5, 08, 37, D3), V(F2, 87, 28, 30), \ + V(B2, A5, BF, 23), V(BA, 6A, 03, 02), V(5C, 82, 16, ED), \ + V(2B, 1C, CF, 8A), V(92, B4, 79, A7), V(F0, F2, 07, F3), \ + V(A1, E2, 69, 4E), V(CD, F4, DA, 65), V(D5, BE, 05, 06), \ + V(1F, 62, 34, D1), V(8A, FE, A6, C4), V(9D, 53, 2E, 34), \ + V(A0, 55, F3, A2), V(32, E1, 8A, 05), V(75, EB, F6, A4), \ + V(39, EC, 83, 0B), V(AA, EF, 60, 40), V(06, 9F, 71, 5E), \ + V(51, 10, 6E, BD), V(F9, 8A, 21, 3E), V(3D, 06, DD, 96), \ + V(AE, 05, 3E, DD), V(46, BD, E6, 4D), V(B5, 8D, 54, 91), \ + V(05, 5D, C4, 71), V(6F, D4, 06, 04), V(FF, 15, 50, 60), \ + V(24, FB, 98, 19), V(97, E9, BD, D6), V(CC, 43, 40, 89), \ + V(77, 9E, D9, 67), V(BD, 42, E8, B0), V(88, 8B, 89, 07), \ + V(38, 5B, 19, E7), V(DB, EE, C8, 79), V(47, 0A, 7C, A1), \ + V(E9, 0F, 42, 7C), V(C9, 1E, 84, F8), V(00, 00, 00, 00), \ + V(83, 86, 80, 09), V(48, ED, 2B, 32), V(AC, 70, 11, 1E), \ + V(4E, 72, 5A, 6C), V(FB, FF, 0E, FD), V(56, 38, 85, 0F), \ + V(1E, D5, AE, 3D), V(27, 39, 2D, 36), V(64, D9, 0F, 0A), \ + V(21, A6, 5C, 68), V(D1, 54, 5B, 9B), V(3A, 2E, 36, 24), \ + V(B1, 67, 0A, 0C), V(0F, E7, 57, 93), V(D2, 96, EE, B4), \ + V(9E, 91, 9B, 1B), V(4F, C5, C0, 80), V(A2, 20, DC, 61), \ + V(69, 4B, 77, 5A), V(16, 1A, 12, 1C), V(0A, BA, 93, E2), \ + V(E5, 2A, A0, C0), V(43, E0, 22, 3C), V(1D, 17, 1B, 12), \ + V(0B, 0D, 09, 0E), V(AD, C7, 8B, F2), V(B9, A8, B6, 2D), \ + V(C8, A9, 1E, 14), V(85, 19, F1, 57), V(4C, 07, 75, AF), \ + V(BB, DD, 99, EE), V(FD, 60, 7F, A3), V(9F, 26, 01, F7), \ + V(BC, F5, 72, 5C), V(C5, 3B, 66, 44), V(34, 7E, FB, 5B), \ + V(76, 29, 43, 8B), V(DC, C6, 23, CB), V(68, FC, ED, B6), \ + V(63, F1, E4, B8), V(CA, DC, 31, D7), V(10, 85, 63, 42), \ + V(40, 22, 97, 13), V(20, 11, C6, 84), V(7D, 24, 4A, 85), \ + V(F8, 3D, BB, D2), V(11, 32, F9, AE), V(6D, A1, 29, C7), \ + V(4B, 2F, 9E, 1D), V(F3, 30, B2, DC), V(EC, 52, 86, 0D), \ + V(D0, E3, C1, 77), V(6C, 16, B3, 2B), V(99, B9, 70, A9), \ + V(FA, 48, 94, 11), V(22, 64, E9, 47), V(C4, 8C, FC, A8), \ + V(1A, 3F, F0, A0), V(D8, 2C, 7D, 56), V(EF, 90, 33, 22), \ + V(C7, 4E, 49, 87), V(C1, D1, 38, D9), V(FE, A2, CA, 8C), \ + V(36, 0B, D4, 98), V(CF, 81, F5, A6), V(28, DE, 7A, A5), \ + V(26, 8E, B7, DA), V(A4, BF, AD, 3F), V(E4, 9D, 3A, 2C), \ + V(0D, 92, 78, 50), V(9B, CC, 5F, 6A), V(62, 46, 7E, 54), \ + V(C2, 13, 8D, F6), V(E8, B8, D8, 90), V(5E, F7, 39, 2E), \ + V(F5, AF, C3, 82), V(BE, 80, 5D, 9F), V(7C, 93, D0, 69), \ + V(A9, 2D, D5, 6F), V(B3, 12, 25, CF), V(3B, 99, AC, C8), \ + V(A7, 7D, 18, 10), V(6E, 63, 9C, E8), V(7B, BB, 3B, DB), \ + V(09, 78, 26, CD), V(F4, 18, 59, 6E), V(01, B7, 9A, EC), \ + V(A8, 9A, 4F, 83), V(65, 6E, 95, E6), V(7E, E6, FF, AA), \ + V(08, CF, BC, 21), V(E6, E8, 15, EF), V(D9, 9B, E7, BA), \ + V(CE, 36, 6F, 4A), V(D4, 09, 9F, EA), V(D6, 7C, B0, 29), \ + V(AF, B2, A4, 31), V(31, 23, 3F, 2A), V(30, 94, A5, C6), \ + V(C0, 66, A2, 35), V(37, BC, 4E, 74), V(A6, CA, 82, FC), \ + V(B0, D0, 90, E0), V(15, D8, A7, 33), V(4A, 98, 04, F1), \ + V(F7, DA, EC, 41), V(0E, 50, CD, 7F), V(2F, F6, 91, 17), \ + V(8D, D6, 4D, 76), V(4D, B0, EF, 43), V(54, 4D, AA, CC), \ + V(DF, 04, 96, E4), V(E3, B5, D1, 9E), V(1B, 88, 6A, 4C), \ + V(B8, 1F, 2C, C1), V(7F, 51, 65, 46), V(04, EA, 5E, 9D), \ + V(5D, 35, 8C, 01), V(73, 74, 87, FA), V(2E, 41, 0B, FB), \ + V(5A, 1D, 67, B3), V(52, D2, DB, 92), V(33, 56, 10, E9), \ + V(13, 47, D6, 6D), V(8C, 61, D7, 9A), V(7A, 0C, A1, 37), \ + V(8E, 14, F8, 59), V(89, 3C, 13, EB), V(EE, 27, A9, CE), \ + V(35, C9, 61, B7), V(ED, E5, 1C, E1), V(3C, B1, 47, 7A), \ + V(59, DF, D2, 9C), V(3F, 73, F2, 55), V(79, CE, 14, 18), \ + V(BF, 37, C7, 73), V(EA, CD, F7, 53), V(5B, AA, FD, 5F), \ + V(14, 6F, 3D, DF), V(86, DB, 44, 78), V(81, F3, AF, CA), \ + V(3E, C4, 68, B9), V(2C, 34, 24, 38), V(5F, 40, A3, C2), \ + V(72, C3, 1D, 16), V(0C, 25, E2, BC), V(8B, 49, 3C, 28), \ + V(41, 95, 0D, FF), V(71, 01, A8, 39), V(DE, B3, 0C, 08), \ + V(9C, E4, B4, D8), V(90, C1, 56, 64), V(61, 84, CB, 7B), \ + V(70, B6, 32, D5), V(74, 5C, 6C, 48), V(42, 57, B8, D0) + +#define V(a, b, c, d) 0x##a##b##c##d +static const uint32_t RT0[256] = {RT}; +#undef V + +#define V(a, b, c, d) 0x##b##c##d##a +static const uint32_t RT1[256] = {RT}; +#undef V + +#define V(a, b, c, d) 0x##c##d##a##b +static const uint32_t RT2[256] = {RT}; +#undef V + +#define V(a, b, c, d) 0x##d##a##b##c +static const uint32_t RT3[256] = {RT}; +#undef V + +#undef RT + +/* + * Round constants + */ +static const uint32_t RCON[10] = { + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, + 0x00000020, 0x00000040, 0x00000080, 0x0000001B, 0x00000036}; + +#else /* AES_ROM_TABLES */ + +/* + * Forward S-box & tables + */ +static uint8_t FSb[256]; +static uint32_t FT0[256]; +static uint32_t FT1[256]; +static uint32_t FT2[256]; +static uint32_t FT3[256]; + +/* + * Reverse S-box & tables + */ +static uint8_t RSb[256]; +static uint32_t RT0[256]; +static uint32_t RT1[256]; +static uint32_t RT2[256]; +static uint32_t RT3[256]; + +/* + * Round constants + */ +static uint32_t RCON[10]; + +/* + * Tables generation code + */ +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24) +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00)) +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0) + +static int aes_init_done = 0; + +static void aes_gen_tables(void) { + int i, x, y, z; + int pow[256]; + int log[256]; + + /* + * compute pow and log tables over GF(2^8) + */ + for (i = 0, x = 1; i < 256; i++) { + pow[i] = x; + log[x] = i; + x = (x ^ XTIME(x)) & 0xFF; + } + + /* + * calculate the round constants + */ + for (i = 0, x = 1; i < 10; i++) { + RCON[i] = (uint32_t)x; + x = XTIME(x) & 0xFF; + } + + /* + * generate the forward and reverse S-boxes + */ + FSb[0x00] = 0x63; + RSb[0x63] = 0x00; + + for (i = 1; i < 256; i++) { + x = pow[255 - log[i]]; + + y = x; + y = ((y << 1) | (y >> 7)) & 0xFF; + x ^= y; + y = ((y << 1) | (y >> 7)) & 0xFF; + x ^= y; + y = ((y << 1) | (y >> 7)) & 0xFF; + x ^= y; + y = ((y << 1) | (y >> 7)) & 0xFF; + x ^= y ^ 0x63; + + FSb[i] = (uint8_t)x; + RSb[x] = (uint8_t)i; + } + + /* + * generate the forward and reverse tables + */ + for (i = 0; i < 256; i++) { + x = FSb[i]; + y = XTIME(x) & 0xFF; + z = (y ^ x) & 0xFF; + + FT0[i] = ((uint32_t)y) ^ ((uint32_t)x << 8) ^ ((uint32_t)x << 16) ^ + ((uint32_t)z << 24); + + FT1[i] = ROTL8(FT0[i]); + FT2[i] = ROTL8(FT1[i]); + FT3[i] = ROTL8(FT2[i]); + + x = RSb[i]; + + RT0[i] = ((uint32_t)MUL(0x0E, x)) ^ ((uint32_t)MUL(0x09, x) << 8) ^ + ((uint32_t)MUL(0x0D, x) << 16) ^ ((uint32_t)MUL(0x0B, x) << 24); + + RT1[i] = ROTL8(RT0[i]); + RT2[i] = ROTL8(RT1[i]); + RT3[i] = ROTL8(RT2[i]); + } +} + +#endif /* AES_ROM_TABLES */ + +void aes_init(aes_context *ctx) { memset(ctx, 0, sizeof(aes_context)); } + +void aes_free(aes_context *ctx) { + if (ctx == NULL) + return; + + zeroize(ctx, sizeof(aes_context)); +} + +/* + * AES key schedule (encryption) + */ +int aes_setkey_enc(aes_context *ctx, const uint8_t *key, unsigned int keybits) { + unsigned int i; + uint32_t *RK; + +#if !defined(AES_ROM_TABLES) + if (aes_init_done == 0) { + aes_gen_tables(); + aes_init_done = 1; + } +#endif + + switch (keybits) { + case 128: + ctx->nr = 10; + break; + case 192: + ctx->nr = 12; + break; + case 256: + ctx->nr = 14; + break; + default: + return (ERR_AES_INVALID_KEY_LENGTH); + } + + ctx->rk = RK = ctx->buf; + + for (i = 0; i < (keybits >> 5); i++) { + GET_UINT32_LE(RK[i], key, i << 2); + } + + switch (ctx->nr) { + case 10: + + for (i = 0; i < 10; i++, RK += 4) { + RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[3] >> 8) & 0xFF]) ^ + ((uint32_t)FSb[(RK[3] >> 16) & 0xFF] << 8) ^ + ((uint32_t)FSb[(RK[3] >> 24) & 0xFF] << 16) ^ + ((uint32_t)FSb[(RK[3]) & 0xFF] << 24); + + RK[5] = RK[1] ^ RK[4]; + RK[6] = RK[2] ^ RK[5]; + RK[7] = RK[3] ^ RK[6]; + } + break; + + case 12: + + for (i = 0; i < 8; i++, RK += 6) { + RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[5] >> 8) & 0xFF]) ^ + ((uint32_t)FSb[(RK[5] >> 16) & 0xFF] << 8) ^ + ((uint32_t)FSb[(RK[5] >> 24) & 0xFF] << 16) ^ + ((uint32_t)FSb[(RK[5]) & 0xFF] << 24); + + RK[7] = RK[1] ^ RK[6]; + RK[8] = RK[2] ^ RK[7]; + RK[9] = RK[3] ^ RK[8]; + RK[10] = RK[4] ^ RK[9]; + RK[11] = RK[5] ^ RK[10]; + } + break; + + case 14: + + for (i = 0; i < 7; i++, RK += 8) { + RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[7] >> 8) & 0xFF]) ^ + ((uint32_t)FSb[(RK[7] >> 16) & 0xFF] << 8) ^ + ((uint32_t)FSb[(RK[7] >> 24) & 0xFF] << 16) ^ + ((uint32_t)FSb[(RK[7]) & 0xFF] << 24); + + RK[9] = RK[1] ^ RK[8]; + RK[10] = RK[2] ^ RK[9]; + RK[11] = RK[3] ^ RK[10]; + + RK[12] = RK[4] ^ ((uint32_t)FSb[(RK[11]) & 0xFF]) ^ + ((uint32_t)FSb[(RK[11] >> 8) & 0xFF] << 8) ^ + ((uint32_t)FSb[(RK[11] >> 16) & 0xFF] << 16) ^ + ((uint32_t)FSb[(RK[11] >> 24) & 0xFF] << 24); + + RK[13] = RK[5] ^ RK[12]; + RK[14] = RK[6] ^ RK[13]; + RK[15] = RK[7] ^ RK[14]; + } + break; + } + + return (0); +} + +/* + * AES key schedule (decryption) + */ +int aes_setkey_dec(aes_context *ctx, const uint8_t *key, unsigned int keybits) { + int i, j, ret; + aes_context cty; + uint32_t *RK; + uint32_t *SK; + + aes_init(&cty); + + ctx->rk = RK = ctx->buf; + + /* Also checks keybits */ + if ((ret = aes_setkey_enc(&cty, key, keybits)) != 0) + goto exit; + + ctx->nr = cty.nr; + + SK = cty.rk + (uintptr_t)cty.nr * 4; + + *RK++ = *SK++; + *RK++ = *SK++; + *RK++ = *SK++; + *RK++ = *SK++; + + for (i = ctx->nr - 1, SK -= 8; i > 0; i--, SK -= 8) { + for (j = 0; j < 4; j++, SK++) { + *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^ + RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]]; + } + } + + *RK++ = *SK++; + *RK++ = *SK++; + *RK++ = *SK++; + *RK++ = *SK++; + +exit: + aes_free(&cty); + + return (ret); +} + +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ + { \ + X0 = *RK++ ^ FT0[(Y0)&0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \ + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \ + \ + X1 = *RK++ ^ FT0[(Y1)&0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \ + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \ + \ + X2 = *RK++ ^ FT0[(Y2)&0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \ + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \ + \ + X3 = *RK++ ^ FT0[(Y3)&0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \ + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \ + } + +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ + { \ + X0 = *RK++ ^ RT0[(Y0)&0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \ + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \ + \ + X1 = *RK++ ^ RT0[(Y1)&0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \ + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \ + \ + X2 = *RK++ ^ RT0[(Y2)&0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \ + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \ + \ + X3 = *RK++ ^ RT0[(Y3)&0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \ + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \ + } + +/* + * AES-ECB block encryption + */ +void aes_encrypt(aes_context *ctx, const uint8_t input[16], + uint8_t output[16]) { + int i; + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; + + RK = ctx->rk; + + GET_UINT32_LE(X0, input, 0); + X0 ^= *RK++; + GET_UINT32_LE(X1, input, 4); + X1 ^= *RK++; + GET_UINT32_LE(X2, input, 8); + X2 ^= *RK++; + GET_UINT32_LE(X3, input, 12); + X3 ^= *RK++; + + for (i = (ctx->nr >> 1) - 1; i > 0; i--) { + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); + AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); + } + + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); + + X0 = *RK++ ^ ((uint32_t)FSb[(Y0)&0xFF]) ^ + ((uint32_t)FSb[(Y1 >> 8) & 0xFF] << 8) ^ + ((uint32_t)FSb[(Y2 >> 16) & 0xFF] << 16) ^ + ((uint32_t)FSb[(Y3 >> 24) & 0xFF] << 24); + + X1 = *RK++ ^ ((uint32_t)FSb[(Y1)&0xFF]) ^ + ((uint32_t)FSb[(Y2 >> 8) & 0xFF] << 8) ^ + ((uint32_t)FSb[(Y3 >> 16) & 0xFF] << 16) ^ + ((uint32_t)FSb[(Y0 >> 24) & 0xFF] << 24); + + X2 = *RK++ ^ ((uint32_t)FSb[(Y2)&0xFF]) ^ + ((uint32_t)FSb[(Y3 >> 8) & 0xFF] << 8) ^ + ((uint32_t)FSb[(Y0 >> 16) & 0xFF] << 16) ^ + ((uint32_t)FSb[(Y1 >> 24) & 0xFF] << 24); + + X3 = *RK++ ^ ((uint32_t)FSb[(Y3)&0xFF]) ^ + ((uint32_t)FSb[(Y0 >> 8) & 0xFF] << 8) ^ + ((uint32_t)FSb[(Y1 >> 16) & 0xFF] << 16) ^ + ((uint32_t)FSb[(Y2 >> 24) & 0xFF] << 24); + + PUT_UINT32_LE(X0, output, 0); + PUT_UINT32_LE(X1, output, 4); + PUT_UINT32_LE(X2, output, 8); + PUT_UINT32_LE(X3, output, 12); +} + +/* + * AES-ECB block decryption + */ +void aes_decrypt(aes_context *ctx, const uint8_t input[16], + uint8_t output[16]) { + int i; + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; + + RK = ctx->rk; + + GET_UINT32_LE(X0, input, 0); + X0 ^= *RK++; + GET_UINT32_LE(X1, input, 4); + X1 ^= *RK++; + GET_UINT32_LE(X2, input, 8); + X2 ^= *RK++; + GET_UINT32_LE(X3, input, 12); + X3 ^= *RK++; + + for (i = (ctx->nr >> 1) - 1; i > 0; i--) { + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); + AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); + } + + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); + + X0 = *RK++ ^ ((uint32_t)RSb[(Y0)&0xFF]) ^ + ((uint32_t)RSb[(Y3 >> 8) & 0xFF] << 8) ^ + ((uint32_t)RSb[(Y2 >> 16) & 0xFF] << 16) ^ + ((uint32_t)RSb[(Y1 >> 24) & 0xFF] << 24); + + X1 = *RK++ ^ ((uint32_t)RSb[(Y1)&0xFF]) ^ + ((uint32_t)RSb[(Y0 >> 8) & 0xFF] << 8) ^ + ((uint32_t)RSb[(Y3 >> 16) & 0xFF] << 16) ^ + ((uint32_t)RSb[(Y2 >> 24) & 0xFF] << 24); + + X2 = *RK++ ^ ((uint32_t)RSb[(Y2)&0xFF]) ^ + ((uint32_t)RSb[(Y1 >> 8) & 0xFF] << 8) ^ + ((uint32_t)RSb[(Y0 >> 16) & 0xFF] << 16) ^ + ((uint32_t)RSb[(Y3 >> 24) & 0xFF] << 24); + + X3 = *RK++ ^ ((uint32_t)RSb[(Y3)&0xFF]) ^ + ((uint32_t)RSb[(Y2 >> 8) & 0xFF] << 8) ^ + ((uint32_t)RSb[(Y1 >> 16) & 0xFF] << 16) ^ + ((uint32_t)RSb[(Y0 >> 24) & 0xFF] << 24); + + PUT_UINT32_LE(X0, output, 0); + PUT_UINT32_LE(X1, output, 4); + PUT_UINT32_LE(X2, output, 8); + PUT_UINT32_LE(X3, output, 12); +} + +/* + * AES-ECB block encryption/decryption + */ +int aes_crypt_ecb(aes_context *ctx, int mode, const uint8_t input[16], + uint8_t output[16]) { + if (mode == AES_ENCRYPT) + aes_encrypt(ctx, input, output); + else + aes_decrypt(ctx, input, output); + + return (0); +} + +/* + * AES-CBC buffer encryption/decryption + */ +int aes_crypt_cbc(aes_context *ctx, int mode, size_t length, uint8_t iv[16], + const uint8_t *input, uint8_t *output) { + int i; + uint8_t temp[16]; + + if (length % 16) + return (ERR_AES_INVALID_INPUT_LENGTH); + + if (mode == AES_DECRYPT) { + while (length > 0) { + memcpy(temp, input, 16); + aes_crypt_ecb(ctx, mode, input, output); + + for (i = 0; i < 16; i++) + output[i] = (uint8_t)(output[i] ^ iv[i]); + + memcpy(iv, temp, 16); + + input += 16; + output += 16; + length -= 16; + } + } else { + while (length > 0) { + for (i = 0; i < 16; i++) + output[i] = (uint8_t)(input[i] ^ iv[i]); + + aes_crypt_ecb(ctx, mode, output, output); + memcpy(iv, output, 16); + + input += 16; + output += 16; + length -= 16; + } + } + + return (0); +} + +/* + * AES-CFB128 buffer encryption/decryption + */ +int aes_crypt_cfb128(aes_context *ctx, int mode, size_t length, size_t *iv_off, + uint8_t iv[16], const uint8_t *input, uint8_t *output) { + int c; + size_t n = *iv_off; + + if (mode == AES_DECRYPT) { + while (length--) { + if (n == 0) + aes_crypt_ecb(ctx, AES_ENCRYPT, iv, iv); + + c = *input++; + *output++ = (uint8_t)(c ^ iv[n]); + iv[n] = (uint8_t)c; + + n = (n + 1) & 0x0F; + } + } else { + while (length--) { + if (n == 0) + aes_crypt_ecb(ctx, AES_ENCRYPT, iv, iv); + + iv[n] = *output++ = (uint8_t)(iv[n] ^ *input++); + + n = (n + 1) & 0x0F; + } + } + + *iv_off = n; + + return (0); +} + +/* + * AES-CFB8 buffer encryption/decryption + */ +int aes_crypt_cfb8(aes_context *ctx, int mode, size_t length, uint8_t iv[16], + const uint8_t *input, uint8_t *output) { + uint8_t c; + uint8_t ov[17]; + + while (length--) { + memcpy(ov, iv, 16); + aes_crypt_ecb(ctx, AES_ENCRYPT, iv, iv); + + if (mode == AES_DECRYPT) + ov[16] = *input; + + c = *output++ = (uint8_t)(iv[0] ^ *input++); + + if (mode == AES_ENCRYPT) + ov[16] = c; + + memcpy(iv, ov + 1, 16); + } + + return (0); +} + +/* + * AES-CTR buffer encryption/decryption + */ +int aes_crypt_ctr(aes_context *ctx, size_t length, size_t *nc_off, + uint8_t nonce_counter[16], uint8_t stream_block[16], + const uint8_t *input, uint8_t *output) { + int c, i; + size_t n = *nc_off; + + while (length--) { + if (n == 0) { + aes_crypt_ecb(ctx, AES_ENCRYPT, nonce_counter, stream_block); + + for (i = 16; i > 0; i--) + if (++nonce_counter[i - 1] != 0) + break; + } + c = *input++; + *output++ = (uint8_t)(c ^ stream_block[n]); + + n = (n + 1) & 0x0F; + } + + *nc_off = n; + + return (0); +} diff --git a/cdecrypt/aes.h b/cdecrypt/aes.h new file mode 100644 index 0000000..c249765 --- /dev/null +++ b/cdecrypt/aes.h @@ -0,0 +1,236 @@ +/** + * \file aes.h + * + * \brief AES block cipher + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) and was downloaded from + * https://github.com/Secure-Embedded-Systems/RSA-example/tree/master/include/. + */ +#ifndef AES_H +#define AES_H + +#include +#include + +#define AES_ENCRYPT 1 +#define AES_DECRYPT 0 + +#define ERR_AES_INVALID_KEY_LENGTH -0x0020 /**< Invalid key length. */ +#define ERR_AES_INVALID_INPUT_LENGTH -0x0022 /**< Invalid data input length. \ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief AES context structure + * + * \note buf is able to hold 32 extra bytes, which can be used: + * - for alignment purposes if VIA padlock is used, and/or + * - to simplify key expansion in the 256-bit case by + * generating an extra round key + */ +typedef struct { + int nr; /*!< number of rounds */ + uint32_t *rk; /*!< AES round keys */ + uint32_t buf[68]; /*!< unaligned data */ +} aes_context; + +/** + * \brief Initialize AES context + * + * \param ctx AES context to be initialized + */ +void aes_init(aes_context *ctx); + +/** + * \brief Clear AES context + * + * \param ctx AES context to be cleared + */ +void aes_free(aes_context *ctx); + +/** + * \brief AES key schedule (encryption) + * + * \param ctx AES context to be initialized + * \param key encryption key + * \param keybits must be 128, 192 or 256 + * + * \return 0 if successful, or ERR_AES_INVALID_KEY_LENGTH + */ +int aes_setkey_enc(aes_context *ctx, const uint8_t *key, unsigned int keybits); + +/** + * \brief AES key schedule (decryption) + * + * \param ctx AES context to be initialized + * \param key decryption key + * \param keybits must be 128, 192 or 256 + * + * \return 0 if successful, or ERR_AES_INVALID_KEY_LENGTH + */ +int aes_setkey_dec(aes_context *ctx, const uint8_t *key, unsigned int keybits); + +/** + * \brief AES-ECB block encryption/decryption + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param input 16-byte input block + * \param output 16-byte output block + * + * \return 0 if successful + */ +int aes_crypt_ecb(aes_context *ctx, int mode, const uint8_t input[16], + uint8_t output[16]); + +/** + * \brief AES-CBC buffer encryption/decryption + * Length should be a multiple of the block + * size (16 bytes) + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful, or ERR_AES_INVALID_INPUT_LENGTH + */ +int aes_crypt_cbc(aes_context *ctx, int mode, size_t length, uint8_t iv[16], + const uint8_t *input, uint8_t *output); + +/** + * \brief AES-CFB128 buffer encryption/decryption. + * + * Note: Due to the nature of CFB you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv_off offset in IV (updated after use) + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful + */ +int aes_crypt_cfb128(aes_context *ctx, int mode, size_t length, size_t *iv_off, + uint8_t iv[16], const uint8_t *input, uint8_t *output); + +/** + * \brief AES-CFB8 buffer encryption/decryption. + * + * Note: Due to the nature of CFB you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful + */ +int aes_crypt_cfb8(aes_context *ctx, int mode, size_t length, uint8_t iv[16], + const uint8_t *input, uint8_t *output); + +/** + * \brief AES-CTR buffer encryption/decryption + * + * Warning: You have to keep the maximum use of your counter in mind! + * + * Note: Due to the nature of CTR you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \param ctx AES context + * \param length The length of the data + * \param nc_off The offset in the current stream_block (for resuming + * within current cipher stream). The offset pointer to + * should be 0 at the start of a stream. + * \param nonce_counter The 128-bit nonce and counter. + * \param stream_block The saved stream-block for resuming. Is overwritten + * by the function. + * \param input The input data stream + * \param output The output data stream + * + * \return 0 if successful + */ +int aes_crypt_ctr(aes_context *ctx, size_t length, size_t *nc_off, + uint8_t nonce_counter[16], uint8_t stream_block[16], + const uint8_t *input, uint8_t *output); + +/** + * \brief Internal AES block encryption function + * (Only exposed to allow overriding it, + * see AES_ENCRYPT_ALT) + * + * \param ctx AES context + * \param input Plaintext block + * \param output Output (ciphertext) block + */ +void aes_encrypt(aes_context *ctx, const uint8_t input[16], uint8_t output[16]); + +/** + * \brief Internal AES block decryption function + * (Only exposed to allow overriding it, + * see AES_DECRYPT_ALT) + * + * \param ctx AES context + * \param input Ciphertext block + * \param output Output (plaintext) block + */ +void aes_decrypt(aes_context *ctx, const uint8_t input[16], uint8_t output[16]); + +#ifdef __cplusplus +} +#endif + +#endif /* aes.h */ diff --git a/cdecrypt/cdecrypt.c b/cdecrypt/cdecrypt.c new file mode 100644 index 0000000..a710b8a --- /dev/null +++ b/cdecrypt/cdecrypt.c @@ -0,0 +1,608 @@ +/* + cdecrypt - Decrypt Wii U NUS content files + + Copyright © 2013-2015 crediar + Copyright © 2020-2022 VitaSmith + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "aes.h" +#include "cdecrypt.h" +#include "sha1.h" +#include "utf8.h" +#include "util.h" + +#define MAX_ENTRIES 90000 +#define MAX_LEVELS 16 +#define FST_MAGIC 0x46535400 // 'FST\0' +// We use part of the root cert name used by TMD/TIK to identify them +#define TMD_MAGIC 0x4350303030303030ULL // 'CP000000' +#define TIK_MAGIC 0x5853303030303030ULL // 'XS000000' +#define T_MAGIC_OFFSET 0x0150 +#define HASH_BLOCK_SIZE 0xFC00 +#define HASHES_SIZE 0x0400 + +static const uint8_t WiiUCommonDevKey[16] = {0x2F, 0x5C, 0x1B, 0x29, 0x44, 0xE7, + 0xFD, 0x6F, 0xC3, 0x97, 0x96, 0x4B, + 0x05, 0x76, 0x91, 0xFA}; +static const uint8_t WiiUCommonKey[16] = {0xD7, 0xB0, 0x04, 0x02, 0x65, 0x9B, + 0xA2, 0xAB, 0xD2, 0xCB, 0x0D, 0xB2, + 0x7F, 0xA2, 0xB6, 0x56}; + +aes_context ctx; +uint8_t title_id[16]; +uint8_t title_key[16]; +uint64_t h0_count = 0; +uint64_t h0_fail = 0; + +#pragma pack(1) + +enum ContentType { + CONTENT_REQUIRED = (1 << 0), // Not sure + CONTENT_SHARED = (1 << 15), + CONTENT_OPTIONAL = (1 << 14), +}; + +typedef struct { + uint16_t IndexOffset; // 0 0x204 + uint16_t CommandCount; // 2 0x206 + uint8_t SHA2[32]; // 12 0x208 +} ContentInfo; + +typedef struct { + uint32_t ID; // 0 0xB04 + uint16_t Index; // 4 0xB08 + uint16_t Type; // 6 0xB0A + uint64_t Size; // 8 0xB0C + uint8_t SHA2[32]; // 16 0xB14 +} Content; + +typedef struct { + uint32_t SignatureType; // 0x000 + uint8_t Signature[0x100]; // 0x004 + + uint8_t Padding0[0x3C]; // 0x104 + uint8_t Issuer[0x40]; // 0x140 + + uint8_t Version; // 0x180 + uint8_t CACRLVersion; // 0x181 + uint8_t SignerCRLVersion; // 0x182 + uint8_t Padding1; // 0x183 + + uint64_t SystemVersion; // 0x184 + uint64_t TitleID; // 0x18C + uint32_t TitleType; // 0x194 + uint16_t GroupID; // 0x198 + uint8_t Reserved[62]; // 0x19A + uint32_t AccessRights; // 0x1D8 + uint16_t TitleVersion; // 0x1DC + uint16_t ContentCount; // 0x1DE + uint16_t BootIndex; // 0x1E0 + uint8_t Padding3[2]; // 0x1E2 + uint8_t SHA2[32]; // 0x1E4 + + ContentInfo ContentInfos[64]; + + Content Contents[]; // 0x1E4 + +} TitleMetaData; + +struct FSTInfo { + uint32_t Unknown; + uint32_t Size; + uint32_t UnknownB; + uint32_t UnknownC[6]; +}; + +struct FST { + uint32_t MagicBytes; + uint32_t Unknown; + uint32_t EntryCount; + + uint32_t UnknownB[5]; + + struct FSTInfo FSTInfos[]; +}; + +struct FEntry { + union { + struct { + uint32_t Type : 8; + uint32_t NameOffset : 24; + }; + uint32_t TypeName; + }; + union { + struct // File Entry + { + uint32_t FileOffset; + uint32_t FileLength; + }; + struct // Dir Entry + { + uint32_t ParentOffset; + uint32_t NextOffset; + }; + uint32_t entry[2]; + }; + uint16_t Flags; + uint16_t ContentID; +}; + +static bool file_dump(const char *path, void *buf, size_t len) { + assert(buf != NULL); + assert(len != 0); + + FILE *dst = fopen_utf8(path, "wb"); + if (dst == NULL) { + fprintf(stderr, "ERROR: Could not dump file '%s'\n", path); + return false; + } + + bool r = (fwrite(buf, 1, len, dst) == len); + if (!r) + fprintf(stderr, "ERROR: Failed to dump file '%s'\n", path); + + fclose(dst); + return r; +} + +static __inline char ascii(char s) { + if (s < 0x20) + return '.'; + if (s > 0x7E) + return '.'; + return s; +} + +static void hexdump(uint8_t *buf, size_t len) { + size_t i, off; + for (off = 0; off < len; off += 16) { + printf("%08x ", (uint32_t)off); + for (i = 0; i < 16; i++) + if ((i + off) >= len) + printf(" "); + else + printf("%02x ", buf[off + i]); + + printf(" "); + for (i = 0; i < 16; i++) { + if ((i + off) >= len) + printf(" "); + else + printf("%c", ascii(buf[off + i])); + } + printf("\n"); + } +} + +#define BLOCK_SIZE 0x10000 +static bool extract_file_hash(FILE *src, uint64_t part_data_offset, + uint64_t file_offset, uint64_t size, + const char *path, uint16_t content_id) { + bool r = false; + uint8_t *enc = malloc(BLOCK_SIZE); + uint8_t *dec = malloc(BLOCK_SIZE); + assert(enc != NULL); + assert(dec != NULL); + uint8_t iv[16]; + uint8_t hash[SHA_DIGEST_LENGTH]; + uint8_t h0[SHA_DIGEST_LENGTH]; + uint8_t hashes[HASHES_SIZE]; + + uint64_t write_size = HASH_BLOCK_SIZE; + uint64_t block_number = (file_offset / HASH_BLOCK_SIZE) & 0x0F; + + FILE *dst = fopen_utf8(path, "wb"); + if (dst == NULL) { + fprintf(stderr, "ERROR: Could not create '%s'\n", path); + goto out; + } + + uint64_t roffset = file_offset / HASH_BLOCK_SIZE * BLOCK_SIZE; + uint64_t soffset = + file_offset - (file_offset / HASH_BLOCK_SIZE * HASH_BLOCK_SIZE); + + if (soffset + size > write_size) + write_size = write_size - soffset; + + fseek64(src, part_data_offset + roffset, SEEK_SET); + while (size > 0) { + if (write_size > size) + write_size = size; + + if (fread(enc, sizeof(char), BLOCK_SIZE, src) != BLOCK_SIZE) { + fprintf(stderr, "ERROR: Could not read %d bytes from '%s'\n", BLOCK_SIZE, + path); + goto out; + } + + memset(iv, 0, sizeof(iv)); + iv[1] = (uint8_t)content_id; + aes_crypt_cbc(&ctx, AES_DECRYPT, HASHES_SIZE, iv, enc, (uint8_t *)hashes); + + memcpy(h0, hashes + 0x14 * block_number, SHA_DIGEST_LENGTH); + + memcpy(iv, hashes + 0x14 * block_number, sizeof(iv)); + if (block_number == 0) + iv[1] ^= content_id; + aes_crypt_cbc(&ctx, AES_DECRYPT, HASH_BLOCK_SIZE, iv, enc + HASHES_SIZE, + dec); + + sha1(dec, HASH_BLOCK_SIZE, hash); + + if (block_number == 0) + hash[1] ^= content_id; + h0_count++; + if (memcmp(hash, h0, SHA_DIGEST_LENGTH) != 0) { + h0_fail++; + hexdump(hash, SHA_DIGEST_LENGTH); + hexdump(hashes, 0x100); + hexdump(dec, 0x100); + fprintf(stderr, "ERROR: Could not verify H0 hash\n"); + goto out; + } + + size -= fwrite(dec + soffset, sizeof(char), (size_t)write_size, dst); + + block_number++; + if (block_number >= 16) + block_number = 0; + + if (soffset) { + write_size = HASH_BLOCK_SIZE; + soffset = 0; + } + } + r = true; + +out: + if (dst != NULL) + fclose(dst); + free(enc); + free(dec); + return r; +} +#undef BLOCK_SIZE + +#define BLOCK_SIZE 0x8000 +static bool extract_file(FILE *src, uint64_t part_data_offset, + uint64_t file_offset, uint64_t size, const char *path, + uint16_t content_id) { + bool r = false; + uint8_t *enc = malloc(BLOCK_SIZE); + uint8_t *dec = malloc(BLOCK_SIZE); + assert(enc != NULL); + assert(dec != NULL); + + // Calc real offset + uint64_t roffset = file_offset / BLOCK_SIZE * BLOCK_SIZE; + uint64_t soffset = file_offset - (file_offset / BLOCK_SIZE * BLOCK_SIZE); + + FILE *dst = fopen_utf8(path, "wb"); + if (dst == NULL) { + fprintf(stderr, "ERROR: Could not create '%s'\n", path); + goto out; + } + uint8_t iv[16]; + memset(iv, 0, sizeof(iv)); + iv[1] = (uint8_t)content_id; + + uint64_t write_size = BLOCK_SIZE; + + if (soffset + size > write_size) + write_size = write_size - soffset; + + fseek64(src, part_data_offset + roffset, SEEK_SET); + + while (size > 0) { + if (write_size > size) + write_size = size; + + if (fread(enc, sizeof(char), BLOCK_SIZE, src) != BLOCK_SIZE) { + fprintf(stderr, "ERROR: Could not read %d bytes from '%s'\n", BLOCK_SIZE, + path); + goto out; + } + + aes_crypt_cbc(&ctx, AES_DECRYPT, BLOCK_SIZE, iv, (const uint8_t *)(enc), + (uint8_t *)dec); + + size -= fwrite(dec + soffset, sizeof(char), (size_t)write_size, dst); + + if (soffset) { + write_size = BLOCK_SIZE; + soffset = 0; + } + } + + r = true; + +out: + if (dst != NULL) + fclose(dst); + free(enc); + free(dec); + return r; +} +#undef BLOCK_SIZE + +int cdecrypt_main(int argc, char **argv) { + int r = EXIT_FAILURE; + char str[PATH_MAX], *tmd_path = NULL, *tik_path = NULL; + FILE *src = NULL; + TitleMetaData *tmd = NULL; + uint8_t *tik = NULL, *cnt = NULL; + const char *pattern[] = {"%s%c%08x.app", "%s%c%08X.app", "%s%c%08x", + "%s%c%08X"}; + + if (argc < 2) { + printf( + "%s %s - Wii U NUS content file decrypter\n" + "Copyright (c) 2020-2023 VitaSmith, Copyright (c) 2013-2015 crediar\n" + "Visit https://github.com/VitaSmith/cdecrypt for official source and " + "downloads.\n\n" + "Usage: %s \n\n" + "This program is free software; you can redistribute it and/or modify " + "it under\n" + "the terms of the GNU General Public License as published by the Free " + "Software\n" + "Foundation; either version 3 of the License or any later version.\n", + _appname(argv[0]), APP_VERSION_STR, _appname(argv[0])); + return EXIT_SUCCESS; + } + + if (!is_directory(argv[1])) { + uint8_t *buf = NULL; + uint32_t size = + read_file_max(argv[1], &buf, T_MAGIC_OFFSET + sizeof(uint64_t)); + if (size == 0) + goto out; + if (size >= T_MAGIC_OFFSET + sizeof(uint64_t)) { + uint64_t magic = getbe64(&buf[T_MAGIC_OFFSET]); + free(buf); + if (magic == TMD_MAGIC) { + tmd_path = strdup(argv[1]); + if (argc < 3) { + tik_path = strdup(argv[1]); + tik_path[strlen(tik_path) - 2] = 'i'; + tik_path[strlen(tik_path) - 1] = 'k'; + } else { + tik_path = strdup(argv[2]); + } + } else if (magic == TIK_MAGIC) { + tik_path = strdup(argv[1]); + if (argc < 3) { + tmd_path = strdup(argv[1]); + tmd_path[strlen(tik_path) - 2] = 'm'; + tmd_path[strlen(tik_path) - 1] = 'd'; + } else { + tmd_path = strdup(argv[2]); + } + } + } + + // We'll need the current path for locating files, which we set in argv[1] + argv[1][get_trailing_slash(argv[1])] = 0; + if (argv[1][0] == 0) { + argv[1][0] = '.'; + argv[1][1] = 0; + } + } + + // If the condition below is true, argv[1] is a directory + if ((tmd_path == NULL) || (tik_path == NULL)) { + size_t size = strlen(argv[1]); + free(tmd_path); + free(tik_path); + tmd_path = calloc(size + 16, 1); + tik_path = calloc(size + 16, 1); + sprintf(tmd_path, "%s%ctitle.tmd", argv[1], PATH_SEP); + sprintf(tik_path, "%s%ctitle.tik", argv[1], PATH_SEP); + } + + uint32_t tmd_len = read_file(tmd_path, (uint8_t **)&tmd); + if (tmd_len == 0) + goto out; + + uint32_t tik_len = read_file(tik_path, &tik); + if (tik_len == 0) + goto out; + + if (tmd->Version != 1) { + fprintf(stderr, "ERROR: Unsupported TMD version: %u\n", tmd->Version); + goto out; + } + + printf("Title version:%u\n", getbe16(&tmd->TitleVersion)); + printf("Content count:%u\n", getbe16(&tmd->ContentCount)); + + if (strcmp((char *)(&tmd->Issuer), "Root-CA00000003-CP0000000b") == 0) { + aes_setkey_dec(&ctx, WiiUCommonKey, sizeof(WiiUCommonKey) * 8); + } else if (strcmp((char *)(&tmd->Issuer), "Root-CA00000004-CP00000010") == + 0) { + aes_setkey_dec(&ctx, WiiUCommonDevKey, sizeof(WiiUCommonDevKey) * 8); + } else { + fprintf(stderr, "ERROR: Unknown Root type: '%s'\n", (char *)tmd + 0x140); + goto out; + } + + memset(title_id, 0, sizeof(title_id)); + + memcpy(title_id, &tmd->TitleID, 8); + memcpy(title_key, tik + 0x1BF, 16); + + aes_crypt_cbc(&ctx, AES_DECRYPT, sizeof(title_key), title_id, title_key, + title_key); + aes_setkey_dec(&ctx, title_key, sizeof(title_key) * 8); + + uint8_t iv[16]; + memset(iv, 0, sizeof(iv)); + + for (uint32_t k = 0; k < (array_size(pattern) / 2); k++) { + sprintf(str, pattern[k], argv[1], PATH_SEP, getbe32(&tmd->Contents[0].ID)); + if (is_file(str)) + break; + } + + uint32_t cnt_len = read_file(str, &cnt); + if (cnt_len == 0) { + for (uint32_t k = (array_size(pattern) / 2); k < array_size(pattern); k++) { + sprintf(str, pattern[k], argv[1], PATH_SEP, + getbe32(&tmd->Contents[0].ID)); + if (is_file(str)) + break; + } + cnt_len = read_file(str, &cnt); + if (cnt_len == 0) + goto out; + } + + if (getbe64(&tmd->Contents[0].Size) != (uint64_t)cnt_len) { + fprintf(stderr, "ERROR: Size of content %u is wrong: %u:%" PRIu64 "\n", + getbe32(&tmd->Contents[0].ID), cnt_len, + getbe64(&tmd->Contents[0].Size)); + goto out; + } + + aes_crypt_cbc(&ctx, AES_DECRYPT, cnt_len, iv, cnt, cnt); + + if (getbe32(cnt) != FST_MAGIC) { + sprintf(str, "%s%c%08X.dec", argv[1], PATH_SEP, + getbe32(&tmd->Contents[0].ID)); + fprintf( + stderr, + "ERROR: Unexpected content magic. Dumping decrypted file as '%s'.\n", + str); + file_dump(str, cnt, cnt_len); + goto out; + } + + struct FST *fst = (struct FST *)cnt; + + printf("FSTInfo Entries: %u\n", getbe32(&fst->EntryCount)); + if (getbe32(&fst->EntryCount) > MAX_ENTRIES) { + fprintf(stderr, "ERROR: Too many entries\n"); + goto out; + } + + struct FEntry *fe = + (struct FEntry *)(cnt + 0x20 + + (uintptr_t)getbe32(&fst->EntryCount) * 0x20); + + uint32_t entries = + getbe32(cnt + 0x20 + (uintptr_t)getbe32(&fst->EntryCount) * 0x20 + 8); + uint32_t name_offset = + 0x20 + getbe32(&fst->EntryCount) * 0x20 + entries * 0x10; + + printf("FST entries: %u\n", entries); + + char *dst_dir = ((argc <= 2) || is_file(argv[2])) ? argv[1] : argv[2]; + printf("Extracting to directory: '%s'\n", dst_dir); + create_path(dst_dir); + char path[PATH_MAX] = {0}; + uint32_t entry[16]; + uint32_t l_entry[16]; + + uint32_t level = 0; + + for (uint32_t i = 1; i < entries; i++) { + if (level > 0) { + while ((level >= 1) && (l_entry[level - 1] == i)) + level--; + } + + if (fe[i].Type & 1) { + entry[level] = i; + l_entry[level++] = getbe32(&fe[i].NextOffset); + if (level >= MAX_LEVELS) { + fprintf(stderr, "ERROR: Too many levels\n"); + break; + } + } else { + uint32_t offset; + memset(path, 0, sizeof(path)); + strcpy(path, dst_dir); + + size_t short_path = strlen(path) + 1; + for (uint32_t j = 0; j < level; j++) { + path[strlen(path)] = PATH_SEP; + offset = getbe32(&fe[entry[j]].TypeName) & 0x00FFFFFF; + memcpy(path + strlen(path), cnt + name_offset + offset, + strlen((char *)cnt + name_offset + offset)); + create_path(path); + } + path[strlen(path)] = PATH_SEP; + offset = getbe32(&fe[i].TypeName) & 0x00FFFFFF; + memcpy(path + strlen(path), cnt + name_offset + offset, + strlen((char *)cnt + name_offset + offset)); + + uint64_t cnt_offset = ((uint64_t)getbe32(&fe[i].FileOffset)); + if ((getbe16(&fe[i].Flags) & 4) == 0) + cnt_offset <<= 5; + + printf("Size:%07X Offset:0x%010" PRIx64 " CID:%02X U:%02X %s\n", + getbe32(&fe[i].FileLength), cnt_offset, getbe16(&fe[i].ContentID), + getbe16(&fe[i].Flags), &path[short_path]); + + uint32_t cnt_file_id = + getbe32(&tmd->Contents[getbe16(&fe[i].ContentID)].ID); + + if (!(fe[i].Type & 0x80)) { + uint16_t tmd_flags = tmd->Contents[getbe16(&fe[i].ContentID)].Type; + // Handle upper/lowercase for target as well as files without extension + for (uint32_t k = 0; k < array_size(pattern); k++) { + sprintf(str, pattern[k], argv[1], PATH_SEP, cnt_file_id); + if (is_file(str)) + break; + } + src = fopen_utf8(str, "rb"); + if (src == NULL) { + fprintf(stderr, "ERROR: Could not open: '%s'\n", str); + goto out; + } + if ((getbe16(&tmd_flags) & 0x02)) { + if (!extract_file_hash(src, 0, cnt_offset, getbe32(&fe[i].FileLength), + path, getbe16(&fe[i].ContentID))) + goto out; + } else { + if (!extract_file(src, 0, cnt_offset, getbe32(&fe[i].FileLength), + path, getbe16(&fe[i].ContentID))) + goto out; + } + fclose(src); + src = NULL; + } + } + } + r = EXIT_SUCCESS; + +out: + free(tmd); + free(tik); + free(cnt); + free(tmd_path); + free(tik_path); + if (src != NULL) + fclose(src); + return r; +} diff --git a/cdecrypt/cdecrypt.h b/cdecrypt/cdecrypt.h new file mode 100644 index 0000000..6896f7c --- /dev/null +++ b/cdecrypt/cdecrypt.h @@ -0,0 +1,3 @@ +#pragma once + +int cdecrypt_main(int argc, char **argv); diff --git a/cdecrypt/sha1.c b/cdecrypt/sha1.c new file mode 100644 index 0000000..8cf1fec --- /dev/null +++ b/cdecrypt/sha1.c @@ -0,0 +1,320 @@ +/* + * FIPS-180-1 compliant SHA-1 implementation + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) and was downloaded from + * https://github.com/Secure-Embedded-Systems/RSA-example/tree/master/library/. + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include "sha1.h" + +#include + +/* Implementation that should never be optimized out by the compiler */ +static void zeroize(void *v, size_t n) { + volatile uint8_t *p = (uint8_t *)v; + while (n--) + *p++ = 0; +} + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n, b, i) \ + { \ + (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | \ + ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); \ + } +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n, b, i) \ + { \ + (b)[(i)] = (uint8_t)((n) >> 24); \ + (b)[(i) + 1] = (uint8_t)((n) >> 16); \ + (b)[(i) + 2] = (uint8_t)((n) >> 8); \ + (b)[(i) + 3] = (uint8_t)((n)); \ + } +#endif + +void sha1_init(sha1_context *ctx) { memset(ctx, 0, sizeof(sha1_context)); } + +void sha1_free(sha1_context *ctx) { + if (ctx == NULL) + return; + + zeroize(ctx, sizeof(sha1_context)); +} + +void sha1_clone(sha1_context *dst, const sha1_context *src) { *dst = *src; } + +/* + * SHA-1 context setup + */ +void sha1_starts(sha1_context *ctx) { + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; +} + +void sha1_process(sha1_context *ctx, const uint8_t data[64]) { + uint32_t temp, W[16], A, B, C, D, E; + + GET_UINT32_BE(W[0], data, 0); + GET_UINT32_BE(W[1], data, 4); + GET_UINT32_BE(W[2], data, 8); + GET_UINT32_BE(W[3], data, 12); + GET_UINT32_BE(W[4], data, 16); + GET_UINT32_BE(W[5], data, 20); + GET_UINT32_BE(W[6], data, 24); + GET_UINT32_BE(W[7], data, 28); + GET_UINT32_BE(W[8], data, 32); + GET_UINT32_BE(W[9], data, 36); + GET_UINT32_BE(W[10], data, 40); + GET_UINT32_BE(W[11], data, 44); + GET_UINT32_BE(W[12], data, 48); + GET_UINT32_BE(W[13], data, 52); + GET_UINT32_BE(W[14], data, 56); + GET_UINT32_BE(W[15], data, 60); + +#define S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define R(t) \ + (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ \ + W[t & 0x0F], \ + (W[t & 0x0F] = S(temp, 1))) + +#define P(a, b, c, d, e, x) \ + { \ + e += S(a, 5) + F(b, c, d) + K + x; \ + b = S(b, 30); \ + } + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + +#define F(x, y, z) (z ^ (x & (y ^ z))) +#define K 0x5A827999 + + P(A, B, C, D, E, W[0]); + P(E, A, B, C, D, W[1]); + P(D, E, A, B, C, W[2]); + P(C, D, E, A, B, W[3]); + P(B, C, D, E, A, W[4]); + P(A, B, C, D, E, W[5]); + P(E, A, B, C, D, W[6]); + P(D, E, A, B, C, W[7]); + P(C, D, E, A, B, W[8]); + P(B, C, D, E, A, W[9]); + P(A, B, C, D, E, W[10]); + P(E, A, B, C, D, W[11]); + P(D, E, A, B, C, W[12]); + P(C, D, E, A, B, W[13]); + P(B, C, D, E, A, W[14]); + P(A, B, C, D, E, W[15]); + P(E, A, B, C, D, R(16)); + P(D, E, A, B, C, R(17)); + P(C, D, E, A, B, R(18)); + P(B, C, D, E, A, R(19)); + +#undef K +#undef F + +#define F(x, y, z) (x ^ y ^ z) +#define K 0x6ED9EBA1 + + P(A, B, C, D, E, R(20)); + P(E, A, B, C, D, R(21)); + P(D, E, A, B, C, R(22)); + P(C, D, E, A, B, R(23)); + P(B, C, D, E, A, R(24)); + P(A, B, C, D, E, R(25)); + P(E, A, B, C, D, R(26)); + P(D, E, A, B, C, R(27)); + P(C, D, E, A, B, R(28)); + P(B, C, D, E, A, R(29)); + P(A, B, C, D, E, R(30)); + P(E, A, B, C, D, R(31)); + P(D, E, A, B, C, R(32)); + P(C, D, E, A, B, R(33)); + P(B, C, D, E, A, R(34)); + P(A, B, C, D, E, R(35)); + P(E, A, B, C, D, R(36)); + P(D, E, A, B, C, R(37)); + P(C, D, E, A, B, R(38)); + P(B, C, D, E, A, R(39)); + +#undef K +#undef F + +#define F(x, y, z) ((x & y) | (z & (x | y))) +#define K 0x8F1BBCDC + + P(A, B, C, D, E, R(40)); + P(E, A, B, C, D, R(41)); + P(D, E, A, B, C, R(42)); + P(C, D, E, A, B, R(43)); + P(B, C, D, E, A, R(44)); + P(A, B, C, D, E, R(45)); + P(E, A, B, C, D, R(46)); + P(D, E, A, B, C, R(47)); + P(C, D, E, A, B, R(48)); + P(B, C, D, E, A, R(49)); + P(A, B, C, D, E, R(50)); + P(E, A, B, C, D, R(51)); + P(D, E, A, B, C, R(52)); + P(C, D, E, A, B, R(53)); + P(B, C, D, E, A, R(54)); + P(A, B, C, D, E, R(55)); + P(E, A, B, C, D, R(56)); + P(D, E, A, B, C, R(57)); + P(C, D, E, A, B, R(58)); + P(B, C, D, E, A, R(59)); + +#undef K +#undef F + +#define F(x, y, z) (x ^ y ^ z) +#define K 0xCA62C1D6 + + P(A, B, C, D, E, R(60)); + P(E, A, B, C, D, R(61)); + P(D, E, A, B, C, R(62)); + P(C, D, E, A, B, R(63)); + P(B, C, D, E, A, R(64)); + P(A, B, C, D, E, R(65)); + P(E, A, B, C, D, R(66)); + P(D, E, A, B, C, R(67)); + P(C, D, E, A, B, R(68)); + P(B, C, D, E, A, R(69)); + P(A, B, C, D, E, R(70)); + P(E, A, B, C, D, R(71)); + P(D, E, A, B, C, R(72)); + P(C, D, E, A, B, R(73)); + P(B, C, D, E, A, R(74)); + P(A, B, C, D, E, R(75)); + P(E, A, B, C, D, R(76)); + P(D, E, A, B, C, R(77)); + P(C, D, E, A, B, R(78)); + P(B, C, D, E, A, R(79)); + +#undef K +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; +} + +/* + * SHA-1 process buffer + */ +void sha1_update(sha1_context *ctx, const uint8_t *input, size_t ilen) { + size_t fill; + uint32_t left; + + if (ilen == 0) + return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t)ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if (ctx->total[0] < (uint32_t)ilen) + ctx->total[1]++; + + if (left && ilen >= fill) { + memcpy((void *)(ctx->buffer + left), input, fill); + sha1_process(ctx, ctx->buffer); + input += fill; + ilen -= fill; + left = 0; + } + + while (ilen >= 64) { + sha1_process(ctx, input); + input += 64; + ilen -= 64; + } + + if (ilen > 0) + memcpy((void *)(ctx->buffer + left), input, ilen); +} + +static const uint8_t sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* + * SHA-1 final digest + */ +void sha1_finish(sha1_context *ctx, uint8_t output[SHA_DIGEST_LENGTH]) { + uint32_t last, padn; + uint32_t high, low; + uint8_t msglen[8]; + + high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); + low = (ctx->total[0] << 3); + + PUT_UINT32_BE(high, msglen, 0); + PUT_UINT32_BE(low, msglen, 4); + + last = ctx->total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + sha1_update(ctx, sha1_padding, padn); + sha1_update(ctx, msglen, 8); + + PUT_UINT32_BE(ctx->state[0], output, 0); + PUT_UINT32_BE(ctx->state[1], output, 4); + PUT_UINT32_BE(ctx->state[2], output, 8); + PUT_UINT32_BE(ctx->state[3], output, 12); + PUT_UINT32_BE(ctx->state[4], output, 16); +} + +/* + * output = SHA-1( input buffer ) + */ +void sha1(const uint8_t *input, size_t ilen, + uint8_t output[SHA_DIGEST_LENGTH]) { + sha1_context ctx; + + sha1_init(&ctx); + sha1_starts(&ctx); + sha1_update(&ctx, input, ilen); + sha1_finish(&ctx, output); + sha1_free(&ctx); +} diff --git a/cdecrypt/sha1.h b/cdecrypt/sha1.h new file mode 100644 index 0000000..e45a36c --- /dev/null +++ b/cdecrypt/sha1.h @@ -0,0 +1,107 @@ +/** + * \file sha1.h + * + * \brief SHA-1 cryptographic hash function + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) and was downloaded from + * https://github.com/Secure-Embedded-Systems/RSA-example/tree/master/include/. + */ +#ifndef SHA1_H +#define SHA1_H + +#include +#include + +#define SHA_DIGEST_LENGTH 20 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief SHA-1 context structure + */ +typedef struct { + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[5]; /*!< intermediate digest state */ + uint8_t buffer[64]; /*!< data block being processed */ +} sha1_context; + +/** + * \brief Initialize SHA-1 context + * + * \param ctx SHA-1 context to be initialized + */ +void sha1_init(sha1_context *ctx); + +/** + * \brief Clear SHA-1 context + * + * \param ctx SHA-1 context to be cleared + */ +void sha1_free(sha1_context *ctx); + +/** + * \brief Clone (the state of) a SHA-1 context + * + * \param dst The destination context + * \param src The context to be cloned + */ +void sha1_clone(sha1_context *dst, const sha1_context *src); + +/** + * \brief SHA-1 context setup + * + * \param ctx context to be initialized + */ +void sha1_starts(sha1_context *ctx); + +/** + * \brief SHA-1 process buffer + * + * \param ctx SHA-1 context + * \param input buffer holding the data + * \param ilen length of the input data + */ +void sha1_update(sha1_context *ctx, const uint8_t *input, size_t ilen); + +/** + * \brief SHA-1 final digest + * + * \param ctx SHA-1 context + * \param output SHA-1 checksum result + */ +void sha1_finish(sha1_context *ctx, uint8_t output[SHA_DIGEST_LENGTH]); + +/* Internal use */ +void sha1_process(sha1_context *ctx, const uint8_t data[64]); + +/** + * \brief Output = SHA-1( input buffer ) + * + * \param input buffer holding the data + * \param ilen length of the input data + * \param output SHA-1 checksum result + */ +void sha1(const uint8_t *input, size_t ilen, uint8_t output[SHA_DIGEST_LENGTH]); + +#ifdef __cplusplus +} +#endif + +#endif /* sha1.h */ diff --git a/cdecrypt/utf8.h b/cdecrypt/utf8.h new file mode 100644 index 0000000..89d2309 --- /dev/null +++ b/cdecrypt/utf8.h @@ -0,0 +1,149 @@ +/* + UTF-8 handling for Gust (Koei/Tecmo) PC games tools + Copyright © 2019 VitaSmith + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifdef _DEBUG +#define _CRTDBG_MAP_ALLOC +#include +#include +#define DUMP_LEAKS _CrtDumpMemoryLeaks() +#else +#define DUMP_LEAKS +#endif + +#include +#include +#include +#include +#include + +#pragma once + +#if defined(_WIN32) +#include +#define stat64 _stat64 +#define stat64_t stat64 + +static __inline char *utf16_to_utf8(const wchar_t *str16) { + char *str8 = NULL; + int str8_size = 0; + + if (str16[0] == 0) + return calloc(1, 1); + + if (((str8_size = str8_size = WideCharToMultiByte(CP_UTF8, 0, str16, -1, NULL, + 0, NULL, NULL)) <= 1) || + ((str8 = (char *)calloc(str8_size, 1)) == NULL)) + return NULL; + + if (WideCharToMultiByte(CP_UTF8, 0, str16, -1, str8, str8_size, NULL, NULL) != + str8_size) { + free(str8); + return NULL; + } + + return str8; +} + +static __inline wchar_t *utf8_to_utf16(const char *str8) { + wchar_t *str16 = NULL; + int str16_size = 0; + + if (str8 == NULL) + return NULL; + + if (str8[0] == 0) + return calloc(1, sizeof(wchar_t)); + + if (((str16_size = MultiByteToWideChar(CP_UTF8, 0, str8, -1, NULL, 0)) <= + 1) || + ((str16 = (wchar_t *)calloc(str16_size, sizeof(wchar_t))) == NULL)) + return NULL; + + if (MultiByteToWideChar(CP_UTF8, 0, str8, -1, str16, str16_size) != + str16_size) { + free(str16); + return NULL; + } + + return str16; +} + +static __inline FILE *fopen_utf8(const char *filename, const char *mode) { + FILE *r = NULL; + wchar_t *filename16 = utf8_to_utf16(filename); + wchar_t *mode16 = utf8_to_utf16(mode); + _wfopen_s(&r, filename16, mode16); + free(filename16); + free(mode16); + return r; +} + +static __inline int rename_utf8(const char *oldname, const char *newname) { + wchar_t *oldname16 = utf8_to_utf16(oldname); + wchar_t *newname16 = utf8_to_utf16(newname); + int r = _wrename(oldname16, newname16); + free(oldname16); + free(newname16); + return r; +} + +static __inline int stat64_utf8(const char *path, struct stat64 *buffer) { + int r; + wchar_t *path16 = utf8_to_utf16(path); + r = _wstat64(path16, buffer); + free(path16); + return r; +} + +static __inline BOOL CreateDirectory_utf8(const char *path, + LPSECURITY_ATTRIBUTES attrs) { + BOOL r; + wchar_t *path16 = utf8_to_utf16(path); + r = CreateDirectoryW(path16, attrs); + free(path16); + return r; +} + +#define CALL_MAIN \ + int wmain(int argc, wchar_t **argv16) { \ + SetConsoleOutputCP(CP_UTF8); \ + char **argv = calloc(argc, sizeof(char *)); \ + if (argv == NULL) \ + return EXIT_FAILURE; \ + for (int i = 0; i < argc; i++) \ + argv[i] = utf16_to_utf8(argv16[i]); \ + int r = main_utf8(argc, argv); \ + for (int i = 0; i < argc; i++) \ + free(argv[i]); \ + free(argv); \ + DUMP_LEAKS; \ + return r; \ + } +#else +#define fopen_utf8 fopen +#define rename_utf8 rename +#if defined(__APPLE__) +#define stat64_utf8 stat +#define stat64_t stat +#else +#define stat64_utf8 stat64 +#define stat64_t stat64 +#endif +#define CALL_MAIN \ + int main(int argc, char **argv) { return main_utf8(argc, argv); } +#endif diff --git a/cdecrypt/util.c b/cdecrypt/util.c new file mode 100644 index 0000000..7726785 --- /dev/null +++ b/cdecrypt/util.c @@ -0,0 +1,242 @@ +/* + Common code for Gust (Koei/Tecmo) PC games tools + Copyright © 2019-2021 VitaSmith + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#include +#include +#include +#include + +#include "utf8.h" +#include "util.h" + +bool create_path(char *path) { + bool result = true; + struct stat64_t st; +#if defined(_WIN32) + // Ignore Windows drive names + if ((strlen(path) == 2) && (path[1] == ':')) + return true; +#endif + if (stat64_utf8(path, &st) != 0) { + // Directory doesn't exist, create it + size_t pos = 0; + for (size_t n = strlen(path); n > 0; n--) { + if (path[n] == PATH_SEP) { + while ((n > 0) && (path[--n] == PATH_SEP)) + ; + pos = n + 1; + break; + } + } + if (pos > 0) { + // Create parent dirs + path[pos] = 0; + char *new_path = malloc(strlen(path) + 1); + if (new_path == NULL) { + fprintf(stderr, "ERROR: Can't allocate path\n"); + return false; + } + strcpy(new_path, path); + result = create_path(new_path); + free(new_path); + path[pos] = PATH_SEP; + } + // Create node + if (result) + result = CREATE_DIR(path); + } else if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "ERROR: '%s' exists but isn't a directory\n", path); + return false; + } + + return result; +} + +// dirname/basename, that *PRESERVE* the string parameter. +// Note that these calls are not concurrent, meaning that you MUST be done +// using the returned string from a previous call before invoking again. +#if defined(_WIN32) +char *_basename_win32(const char *path, bool remove_extension) { + static char basename[128]; + static char ext[64]; + ext[0] = 0; + _splitpath_s(path, NULL, 0, NULL, 0, basename, sizeof(basename), ext, + sizeof(ext)); + if ((ext[0] != 0) && !remove_extension) + strncat(basename, ext, sizeof(basename) - strlen(basename)); + return basename; +} + +// This call should behave pretty similar to UNIX' dirname +char *_dirname_win32(const char *path) { + static char dir[PATH_MAX]; + static char drive[4]; + int found_sep = 0; + memset(drive, 0, sizeof(drive)); + _splitpath_s(path, drive, sizeof(drive), dir, sizeof(dir) - 3, NULL, 0, NULL, + 0); + // Only deal with drives that are one letter + drive[2] = 0; + drive[3] = 0; + if (drive[1] != ':') + drive[0] = 0; + // Removing trailing path separators + for (int32_t n = (int32_t)strlen(dir) - 1; + (n > 0) && ((dir[n] == '/') || (dir[n] == '\\')); n--) { + dir[n] = 0; + found_sep++; + } + if (dir[0] == 0) { + if (drive[0] == 0) + return found_sep ? "\\" : "."; + drive[2] = '\\'; + return drive; + } + if (drive[0] != 0) { + // Add the drive + memmove(&dir[2], dir, strlen(dir) + 1); + memcpy(dir, drive, strlen(drive)); + dir[2] = '\\'; + } + return dir; +} +#else +char *_basename_unix(const char *path) { + static char path_copy[PATH_MAX]; + strncpy(path_copy, path, sizeof(path_copy)); + path_copy[PATH_MAX - 1] = 0; + return basename(path_copy); +} + +char *_dirname_unix(const char *path) { + static char path_copy[PATH_MAX]; + strncpy(path_copy, path, sizeof(path_copy)); + path_copy[PATH_MAX - 1] = 0; + return dirname(path_copy); +} +#endif + +bool is_file(const char *path) { + struct stat64_t st; + return (stat64_utf8(path, &st) == 0) && S_ISREG(st.st_mode); +} + +bool is_directory(const char *path) { + struct stat64_t st; + return (stat64_utf8(path, &st) == 0) && S_ISDIR(st.st_mode); +} + +char *change_extension(const char *path, const char *extension) { + static char new_path[PATH_MAX]; + strncpy(new_path, _basename((char *)path), sizeof(new_path) - 1); + for (size_t i = 0; i < sizeof(new_path); i++) { + if (new_path[i] == '.') + new_path[i] = 0; + } + strncat(new_path, extension, sizeof(new_path) - strlen(new_path) - 1); + return new_path; +} + +size_t get_trailing_slash(const char *path) { + size_t i; + if ((path == NULL) || (path[0] == 0)) + return 0; + for (i = strlen(path) - 1; (i > 0) && ((path[i] != '/') && (path[i] != '\\')); + i--) + ; + return (i == 0) ? 0 : i + 1; +} + +uint32_t read_file_max(const char *path, uint8_t **buf, uint32_t max_size) { + FILE *file = fopen_utf8(path, "rb"); + if (file == NULL) { + fprintf(stderr, "ERROR: Can't open '%s'\n", path); + return 0; + } + + fseek(file, 0L, SEEK_END); + uint32_t size = (uint32_t)ftell(file); + fseek(file, 0L, SEEK_SET); + if (max_size != 0) + size = min(size, max_size); + + *buf = calloc(size, 1); + if (*buf == NULL) { + size = 0; + goto out; + } + if (fread(*buf, 1, size, file) != size) { + fprintf(stderr, "ERROR: Can't read '%s'\n", path); + size = 0; + } +out: + fclose(file); + if (size == 0) { + free(*buf); + *buf = NULL; + } + return size; +} + +uint64_t get_file_size(const char *path) { + FILE *file = fopen_utf8(path, "rb"); + if (file == NULL) { + fprintf(stderr, "ERROR: Can't open '%s'\n", path); + return 0; + } + + fseek(file, 0L, SEEK_END); + uint64_t size = ftell64(file); + fclose(file); + return size; +} + +void create_backup(const char *path) { + struct stat64_t st; + if (stat64_utf8(path, &st) == 0) { + char *backup_path = malloc(strlen(path) + 5); + if (backup_path == NULL) + return; + strcpy(backup_path, path); + strcat(backup_path, ".bak"); + if (stat64_utf8(backup_path, &st) != 0) { + if (rename_utf8(path, backup_path) == 0) + printf("Saved backup as '%s'\n", backup_path); + else + fprintf(stderr, "WARNING: Could not create backup file '%s\n", + backup_path); + } + free(backup_path); + } +} + +bool write_file(const uint8_t *buf, const uint32_t size, const char *path, + const bool backup) { + if (backup) + create_backup(path); + FILE *file = fopen_utf8(path, "wb"); + if (file == NULL) { + fprintf(stderr, "ERROR: Can't create file '%s'\n", path); + return false; + } + bool r = (fwrite(buf, 1, size, file) == size); + fclose(file); + if (!r) + fprintf(stderr, "ERROR: Can't write file '%s'\n", path); + return r; +} diff --git a/cdecrypt/util.h b/cdecrypt/util.h new file mode 100644 index 0000000..9f913b3 --- /dev/null +++ b/cdecrypt/util.h @@ -0,0 +1,206 @@ +/* + Common code for Gust (Koei/Tecmo) PC games tools + Copyright © 2019-2021 VitaSmith + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifdef _DEBUG +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif + +#if !defined(_WIN32) +#include +#endif +#include +#include +#include + +#pragma once + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) + +#ifndef APP_VERSION +#define APP_VERSION_STR "[DEV VERSION]" +#else +#define APP_VERSION_STR STRINGIFY(APP_VERSION) +#endif + +#if defined(_WIN32) +#include +#define ftell64 _ftelli64 +#define fseek64 _fseeki64 +#if !defined(S_ISDIR) +#define S_ISDIR(ST_MODE) (((ST_MODE)&_S_IFMT) == _S_IFDIR) +#endif +#if !defined(S_ISREG) +#define S_ISREG(ST_MODE) (((ST_MODE)&_S_IFMT) == _S_IFREG) +#endif +#define CREATE_DIR(path) CreateDirectory_utf8(path, NULL) +#define PATH_SEP '\\' +#else +#if defined(__APPLE__) +#define ftell64 ftello +#define fseek64 fseeko +#else +#define ftell64 ftello64 +#define fseek64 fseeko64 +#endif +#define CREATE_DIR(path) (mkdir(path, 0755) == 0) +#define PATH_SEP '/' +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef array_size +#define array_size(a) (sizeof(a) / sizeof(*a)) +#endif + +#ifndef is_power_of_2 +#define is_power_of_2(x) (((x) & ((x)-1)) == 0) +#endif + +#if defined(_WIN32) +char *_basename_win32(const char *path, bool remove_extension); +char *_dirname_win32(const char *path); +#define _basename(path) _basename_win32(path, false) +#define _appname(path) _basename_win32(path, true) +#define _dirname(path) _dirname_win32(path) +#else +char *_basename_unix(const char *path); +char *_dirname_unix(const char *path); +#define _basename(path) _basename_unix(path) +#define _appname(path) _basename_unix(path) +#define _dirname(path) _dirname_unix(path) +#endif + +#if defined(_MSC_VER) +#include +#define bswap_uint16 _byteswap_ushort +#define bswap_uint32 _byteswap_ulong +#define bswap_uint64 _byteswap_uint64 +#else +#define bswap_uint16 __builtin_bswap16 +#define bswap_uint32 __builtin_bswap32 +#define bswap_uint64 __builtin_bswap64 +#endif + +// Returns the position of the msb. v should be nonzero +static __inline uint32_t find_msb(uint32_t v) { +#if defined(_MSC_VER) + DWORD pos; + _BitScanReverse(&pos, v); + return pos; +#else + return 31 - __builtin_clz(v); +#endif +} + +static __inline uint16_t getle16(const void *p) { + return *(const uint16_t *)(const uint8_t *)(p); +} + +static __inline void setle16(const void *p, const uint16_t v) { + *((uint16_t *)p) = v; +} + +static __inline uint16_t getbe16(const void *p) { + return bswap_uint16(getle16(p)); +} + +static __inline void setbe16(const void *p, const uint16_t v) { + setle16(p, bswap_uint16(v)); +} + +static __inline uint32_t getle24(const void *_p) { + uint8_t *p = (uint8_t *)_p; + return p[0] | (p[1] << 8) | (p[2] << 16); +} + +static __inline void setle24(const void *_p, const uint32_t v) { + uint8_t *p = (uint8_t *)_p; + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; +} + +static __inline uint32_t getbe24(const void *_p) { + uint8_t *p = (uint8_t *)_p; + return (p[0] << 16) | (p[1] << 8) | p[2]; +} + +static __inline void setbe24(const void *_p, const uint32_t v) { + uint8_t *p = (uint8_t *)_p; + p[0] = (v >> 16) & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = v & 0xff; +} + +static __inline uint32_t getle32(const void *p) { + return *(const uint32_t *)(const uint8_t *)(p); +} + +static __inline void setle32(const void *p, const uint32_t v) { + *((uint32_t *)p) = v; +} + +static __inline uint32_t getbe32(const void *p) { + return bswap_uint32(getle32(p)); +} + +static __inline void setbe32(const void *p, const uint32_t v) { + setle32(p, bswap_uint32(v)); +} + +static __inline uint64_t getle64(const void *p) { + return *(const uint64_t *)(const uint8_t *)(p); +} + +static __inline void setle64(const void *p, const uint64_t v) { + *((uint64_t *)p) = v; +} +static __inline uint64_t getbe64(const void *p) { + return bswap_uint64(getle64(p)); +} + +static __inline void setbe64(const void *p, const uint64_t v) { + setle64(p, bswap_uint64(v)); +} + +bool create_path(char *path); +char *change_extension(const char *path, const char *extension); +size_t get_trailing_slash(const char *path); + +bool is_file(const char *path); +bool is_directory(const char *path); + +uint32_t read_file_max(const char *path, uint8_t **buf, uint32_t max_size); +#define read_file(path, buf) read_file_max(path, buf, 0) +uint64_t get_file_size(const char *path); +void create_backup(const char *path); +bool write_file(const uint8_t *buf, const uint32_t size, const char *path, + const bool backup); diff --git a/certificate.go b/certificate.go new file mode 100644 index 0000000..602c020 --- /dev/null +++ b/certificate.go @@ -0,0 +1,49 @@ +package wiiudownloader + +import ( + "fmt" + "os" + + "github.com/cavaliergopher/grab/v3" +) + +var cetkData []byte + +func getCert(tmdData []byte, id int, numContents uint16) ([]byte, error) { + var certSlice []byte + if len(tmdData) == int((0x0B04+0x30*numContents+0xA00)-0x300) { + certSlice = tmdData[0x0B04+0x30*numContents : 0x0B04+0x30*numContents+0xA00-0x300] + } else { + certSlice = tmdData[0x0B04+0x30*numContents : 0x0B04+0x30*numContents+0xA00] + } + switch id { + case 0: + return certSlice[:0x400], nil + case 1: + return certSlice[0x400 : 0x400+0x300], nil + default: + return nil, fmt.Errorf("invalid id: %d", id) + } +} + +func getDefaultCert(client *grab.Client) ([]byte, error) { + if len(cetkData) >= 0x350+0x300 { + return cetkData[0x350 : 0x350+0x300], nil + } + if err := downloadFile(client, "http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/000500101000400a/cetk", "cetk"); err != nil { + return nil, err + } + cetkData, err := os.ReadFile("cetk") + if err != nil { + return nil, err + } + + if err := os.Remove("cetk"); err != nil { + return nil, err + } + + if len(cetkData) >= 0x350+0x300 { + return cetkData[0x350 : 0x350+0x300], nil + } + return nil, fmt.Errorf("failed to download OSv10 cetk, length: %d", len(cetkData)) +} diff --git a/cmd/WiiUDownloader/main.go b/cmd/WiiUDownloader/main.go new file mode 100644 index 0000000..20f0d0f --- /dev/null +++ b/cmd/WiiUDownloader/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello World!") +} diff --git a/decryption.go b/decryption.go new file mode 100644 index 0000000..844dcc8 --- /dev/null +++ b/decryption.go @@ -0,0 +1,25 @@ +package wiiudownloader + +/* +#cgo CFLAGS: -I${SRCDIR}/cdecrypt +#cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/cdecrypt +#cgo LDFLAGS: -L${SRCDIR}/cdecrypt +#cgo LDFLAGS: -lcdecrypt +#include +#include +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +func decryptContents(path string) error { + argv := make([]*C.char, 2) + argv[0] = C.CString("WiiUDownloader") + argv[1] = C.CString(path) + if int(C.cdecrypt_main(2, (**C.char)(unsafe.Pointer(&argv[0])))) != 0 { + return fmt.Errorf("decryption failed") + } + return nil +} diff --git a/downloader.go b/downloader.go new file mode 100644 index 0000000..62708c5 --- /dev/null +++ b/downloader.go @@ -0,0 +1,139 @@ +package wiiudownloader + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/cavaliergopher/grab/v3" +) + +func downloadFile(client *grab.Client, url string, outputPath string) error { + req, err := grab.NewRequest(outputPath, url) + if err != nil { + return err + } + resp := client.Do(req) + if err := resp.Err(); err != nil { + return err + } + + fmt.Printf("[Info] Download saved to ./%v \n", resp.Filename) + return nil +} + +func DownloadTitle(titleID string, outputDirectory string, doDecryption bool) error { + outputDir := strings.TrimRight(outputDirectory, "/\\") + baseURL := fmt.Sprintf("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%s", titleID) + titleKeyBytes, err := hex.DecodeString(titleID) + if err != nil { + return err + } + + if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { + return err + } + + client := grab.NewClient() + downloadURL := fmt.Sprintf("%s/%s", baseURL, "tmd") + tmdPath := filepath.Join(outputDir, "title.tmd") + if err := downloadFile(client, downloadURL, tmdPath); err != nil { + return err + } + + tmdData, err := os.ReadFile(tmdPath) + if err != nil { + return err + } + + var titleVersion uint16 + if err := binary.Read(bytes.NewReader(tmdData[476:478]), binary.BigEndian, &titleVersion); err != nil { + return err + } + + tikPath := filepath.Join(outputDir, "title.tik") + downloadURL = fmt.Sprintf("%s/%s", baseURL, "cetk") + if err := downloadFile(client, downloadURL, tikPath); err != nil { + return err + } + tikData, err := os.ReadFile(tikPath) + if err != nil { + return err + } + encryptedTitleKey := tikData[0x1BF : 0x1BF+0x10] + + var contentCount uint16 + if err := binary.Read(bytes.NewReader(tmdData[478:480]), binary.BigEndian, &contentCount); err != nil { + return err + } + + cert := bytes.Buffer{} + + cert0, err := getCert(tmdData, 0, contentCount) + if err != nil { + return err + } + cert.Write(cert0) + + cert1, err := getCert(tmdData, 1, contentCount) + if err != nil { + return err + } + cert.Write(cert1) + + defaultCert, err := getDefaultCert(client) + if err != nil { + return err + } + cert.Write(defaultCert) + + certPath := filepath.Join(outputDir, "title.cert") + certFile, err := os.Create(certPath) + if err != nil { + return err + } + if err := binary.Write(certFile, binary.BigEndian, cert.Bytes()); err != nil { + return err + } + defer certFile.Close() + fmt.Printf("[Info] Certificate saved to ./%v \n", certPath) + + for i := 0; i < int(contentCount); i++ { + offset := 2820 + (48 * i) + var id uint32 + if err := binary.Read(bytes.NewReader(tmdData[offset:offset+4]), binary.BigEndian, &id); err != nil { + return err + } + + appPath := filepath.Join(outputDir, fmt.Sprintf("%08X.app", id)) + downloadURL = fmt.Sprintf("%s/%08X", baseURL, id) + if err := downloadFile(client, downloadURL, appPath); err != nil { + return err + } + + if tmdData[offset+7]&0x2 == 2 { + h3Path := filepath.Join(outputDir, fmt.Sprintf("%08X.h3", id)) + downloadURL = fmt.Sprintf("%s/%08X.h3", baseURL, id) + if err := downloadFile(client, downloadURL, h3Path); err != nil { + return err + } + var content contentInfo + content.Hash = tmdData[offset+16 : offset+0x14] + content.ID = fmt.Sprintf("%08X", id) + binary.Read(bytes.NewReader(tmdData[offset+8:offset+15]), binary.BigEndian, &content.Size) + if err := checkContentHashes(outputDirectory, encryptedTitleKey, titleKeyBytes, content); err != nil { + return err + } + } + } + + if doDecryption { + decryptContents(outputDir) + } + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ecebce7 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/Xpl0itU/WiiUDownloader + +go 1.20 + +require github.com/cavaliergopher/grab/v3 v3.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0188458 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= +github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= diff --git a/grabTitles.py b/grabTitles.py new file mode 100644 index 0000000..b43da26 --- /dev/null +++ b/grabTitles.py @@ -0,0 +1,23 @@ +#!/bin/env python + +import os +import urllib.request + +# Don't edit below this line + +def checkAndDeleteFile(file): + if os.path.exists(file): + print(f"Deleting {file}") + os.remove(file) + +opener = urllib.request.build_opener() +opener.addheaders = [("User-agent", "NUSspliBuilder/2.1")] +urllib.request.install_opener(opener) + +checkAndDeleteFile("gtitles/gtitles.c") +urllib.request.urlretrieve("https://napi.nbg01.v10lator.de/db", "gtitles/gtitles.c") +os.system("gcc -c -Wall -fpic -Igtitles -o gtitles/gitles.o gtitles/gtitles.c") +os.system("gcc -shared -o gtitles/libgtitles.so gtitles/gitles.o") + +os.system("gcc -c -Wall -fpic -Icdecrypt cdecrypt/*.c") +os.system("gcc -shared -o cdecrypt/libcdecrypt.so cdecrypt/*.o") diff --git a/gtitles.go b/gtitles.go new file mode 100644 index 0000000..17c8a3e --- /dev/null +++ b/gtitles.go @@ -0,0 +1,70 @@ +package wiiudownloader + +/* +#cgo CFLAGS: -I${SRCDIR}/gtitles +#cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/gtitles +#cgo LDFLAGS: -L${SRCDIR}/gtitles +#cgo LDFLAGS: -lgtitles +#include +#include +*/ +import "C" +import "unsafe" + +const ( + MCP_REGION_JAPAN = 0x01 + MCP_REGION_USA = 0x02 + MCP_REGION_EUROPE = 0x04 + MCP_REGION_CHINA = 0x10 + MCP_REGION_KOREA = 0x20 + MCP_REGION_TAIWAN = 0x40 +) + +const ( + TITLE_KEY_mypass = 0 + TITLE_KEY_nintendo = 1 + TITLE_KEY_test = 2 + TITLE_KEY_1234567890 = 3 + TITLE_KEY_Lucy131211 = 4 + TITLE_KEY_fbf10 = 5 + TITLE_KEY_5678 = 6 + TITLE_KEY_1234 = 7 + TITLE_KEY_ = 8 + TITLE_KEY_MAGIC = 9 +) + +const ( + TITLE_CATEGORY_GAME = 0 + TITLE_CATEGORY_UPDATE = 1 + TITLE_CATEGORY_DLC = 2 + TITLE_CATEGORY_DEMO = 3 + TITLE_CATEGORY_ALL = 4 + TITLE_CATEGORY_DISC = 5 +) + +type TitleEntry struct { + name string + titleID uint64 + region uint8 + key uint8 +} + +func getTitleEntries(category uint8) []TitleEntry { + entriesSize := getTitleEntriesSize(category) + entriesSlice := make([]TitleEntry, entriesSize) + cEntries := C.getTitleEntries(C.TITLE_CATEGORY(category)) + cSlice := (*[1 << 28]C.TitleEntry)(unsafe.Pointer(cEntries))[:entriesSize:entriesSize] + for i := 0; i < entriesSize; i++ { + entriesSlice[i] = TitleEntry{ + name: C.GoString(cSlice[i].name), + titleID: uint64(cSlice[i].tid), + region: uint8(cSlice[i].region), + key: uint8(cSlice[i].key), + } + } + return entriesSlice +} + +func getTitleEntriesSize(category uint8) int { + return int(C.getTitleEntriesSize(C.TITLE_CATEGORY(category))) +} diff --git a/gtitles/.gitkeep b/gtitles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gtitles/gtitles.h b/gtitles/gtitles.h new file mode 100644 index 0000000..5bffaee --- /dev/null +++ b/gtitles/gtitles.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * This file is part of NUSspli. * + * Copyright (c) 2022 V10lator * + * * + * 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; either version 3 of the License, or * + * (at your option) any later version. * + * * + * 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, If not, see . * + ***************************************************************************/ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum MCPRegion { + MCP_REGION_JAPAN = 0x01, + MCP_REGION_USA = 0x02, + MCP_REGION_EUROPE = 0x04, + MCP_REGION_CHINA = 0x10, + MCP_REGION_KOREA = 0x20, + MCP_REGION_TAIWAN = 0x40, +} MCPRegion; + +typedef enum { + TID_HIGH_GAME = 0x00050000, + TID_HIGH_DEMO = 0x00050002, + TID_HIGH_SYSTEM_APP = 0x00050010, + TID_HIGH_SYSTEM_DATA = 0x0005001B, + TID_HIGH_SYSTEM_APPLET = 0x00050030, + TID_HIGH_VWII_IOS = 0x00000007, + TID_HIGH_VWII_SYSTEM_APP = 0x00070002, + TID_HIGH_VWII_SYSTEM = 0x00070008, + TID_HIGH_DLC = 0x0005000C, + TID_HIGH_UPDATE = 0x0005000E, +} TID_HIGH; + +#define getTidHighFromTid(tid) ((uint32_t) (tid >> 32)) +#define isGame(tid) (getTidHighFromTid(tid) == TID_HIGH_GAME) +#define isDLC(tid) (getTidHighFromTid(tid) == TID_HIGH_DLC) +#define isUpdate(tid) (getTidHighFromTid(tid) == TID_HIGH_UPDATE) + +typedef enum { + TITLE_CATEGORY_GAME = 0, + TITLE_CATEGORY_UPDATE = 1, + TITLE_CATEGORY_DLC = 2, + TITLE_CATEGORY_DEMO = 3, + TITLE_CATEGORY_ALL = 4, + TITLE_CATEGORY_DISC = 5, +} TITLE_CATEGORY; + +typedef enum { + TITLE_KEY_mypass = 0, + TITLE_KEY_nintendo = 1, + TITLE_KEY_test = 2, + TITLE_KEY_1234567890 = 3, + TITLE_KEY_Lucy131211 = 4, + TITLE_KEY_fbf10 = 5, + TITLE_KEY_5678 = 6, + TITLE_KEY_1234 = 7, + TITLE_KEY_ = 8, + TITLE_KEY_MAGIC = 9, +} TITLE_KEY; + +typedef struct +{ + const char *name; + const uint64_t tid; + const MCPRegion region; + const TITLE_KEY key; +} TitleEntry; + +const TitleEntry *getTitleEntries(TITLE_CATEGORY cat); +size_t getTitleEntriesSize(TITLE_CATEGORY cat); + +#ifdef __cplusplus +} +#endif diff --git a/hash.go b/hash.go new file mode 100644 index 0000000..7437745 --- /dev/null +++ b/hash.go @@ -0,0 +1,99 @@ +package wiiudownloader + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "fmt" + "os" + "reflect" +) + +var commonKey = []byte{0xD7, 0xB0, 0x04, 0x02, 0x65, 0x9B, 0xA2, 0xAB, 0xD2, 0xCB, 0x0D, 0xB2, 0x7F, 0xA2, 0xB6, 0x56} + +func checkContentHashes(path string, encryptedTitleKey []byte, titleID []byte, content contentInfo) error { + c, err := aes.NewCipher(commonKey) + if err != nil { + return fmt.Errorf("failed to create AES cipher: %w", err) + } + + decryptedTitleKey := make([]byte, len(encryptedTitleKey)) + cbc := cipher.NewCBCDecrypter(c, append(titleID, make([]byte, 8)...)) + cbc.CryptBlocks(decryptedTitleKey, encryptedTitleKey) + + h3Data, err := os.ReadFile(fmt.Sprintf("%s/%s.h3", path, content.ID)) + if err != nil { + return fmt.Errorf("failed to read H3 hash tree file: %w", err) + } + encryptedFile, err := os.Open(fmt.Sprintf("%s/%s.app", path, content.ID)) + if err != nil { + return fmt.Errorf("failed to open encrypted file: %w", err) + } + + h3Hash := sha1.Sum(h3Data) + if !reflect.DeepEqual(h3Hash[:8], content.Hash[:8]) { + return fmt.Errorf("h3 Hash mismatch") + } + + chunkCount := int(content.Size / 0x10000) + decryptedContent := make([]byte, content.Size) + + h0HashNum := 0 + h1HashNum := 0 + h2HashNum := 0 + h3HashNum := 0 + + for chunkNum := 0; chunkNum < chunkCount; chunkNum++ { + cipherHashTree, err := aes.NewCipher(decryptedTitleKey) + if err != nil { + return fmt.Errorf("failed to create AES cipher: %w", err) + } + hashTree := cipher.NewCBCDecrypter(cipherHashTree, make([]byte, aes.BlockSize)) + buffer := make([]byte, 0x400) + encryptedFile.Read(buffer) + hashTree.CryptBlocks(decryptedContent, buffer) + + h0Hashes := decryptedContent[0:0x140] + h1Hashes := decryptedContent[0x140:0x280] + h2Hashes := decryptedContent[0x280:0x3c0] + + h1Hash := h1Hashes[(h1HashNum * 0x14):((h1HashNum + 1) * 0x14)] + h2Hash := h2Hashes[(h2HashNum * 0x14):((h2HashNum + 1) * 0x14)] + h3Hash := h3Data[(h3HashNum * 0x14):((h3HashNum + 1) * 0x14)] + + h0HashesHash := sha1.Sum(h0Hashes) + h1HashesHash := sha1.Sum(h1Hashes) + h2HashesHash := sha1.Sum(h2Hashes) + + if !reflect.DeepEqual(h0HashesHash[:], h1Hash) { + return fmt.Errorf("h0 Hashes Hash mismatch") + } + if !reflect.DeepEqual(h1HashesHash[:], h2Hash) { + return fmt.Errorf("h1 Hashes Hash mismatch") + } + if !reflect.DeepEqual(h2HashesHash[:], h3Hash) { + return fmt.Errorf("h2 Hashes Hash mismatch") + } + encryptedFile.Seek(0xFC00, 1) + h0HashNum++ + if h0HashNum >= 16 { + h0HashNum = 0 + h1HashNum++ + } + if h1HashNum >= 16 { + h1HashNum = 0 + h2HashNum++ + } + if h2HashNum >= 16 { + h2HashNum = 0 + h3HashNum++ + } + } + return nil +} + +type contentInfo struct { + ID string + Size int64 + Hash []byte +}