From 7073ee0e4e218080453cee70882b0dbd3fc20d75 Mon Sep 17 00:00:00 2001 From: Myriachan Date: Sat, 20 Mar 2021 19:48:52 -0700 Subject: [PATCH] mechadump 1.00 --- Makefile | 60 ++ mechacrypto/Makefile | 42 ++ mechacrypto/cipher.c | 494 ++++++++++++++ mechacrypto/cipher.h | 41 ++ mechacrypto/crc32.c | 22 + mechacrypto/crc32.h | 16 + mechacrypto/keys.hpp | 83 +++ mechacrypto/sha256.hpp | 143 ++++ mechacrypto/util.c | 216 ++++++ mechacrypto/util.h | 52 ++ mechadump/Makefile | 91 +++ mechadump/common.hpp | 41 ++ mechadump/configexploit.cpp | 364 ++++++++++ mechadump/configexploit.hpp | 28 + mechadump/dumper.cpp | 554 +++++++++++++++ mechadump/dumper.hpp | 15 + mechadump/mechadump.cpp | 1046 +++++++++++++++++++++++++++++ mechadump/sysinfo.cpp | 136 ++++ mechadump/sysinfo.hpp | 9 + mechapatchtool/Makefile | 35 + mechapatchtool/mechapatchtool.cpp | 911 +++++++++++++++++++++++++ patch/Makefile | 43 ++ patch/patch-cdprotect-hook.S | 243 +++++++ patch/patch-irq-hook.S | 242 +++++++ patch/patch.ld | 31 + payload/Makefile | 61 ++ payload/payload-crt0.S | 71 ++ payload/payload-fastdump.c | 115 ++++ payload/payload-keystoredump.c | 49 ++ payload/payload-writenvm.c | 92 +++ payload/payload.ld | 45 ++ 31 files changed, 5391 insertions(+) create mode 100644 Makefile create mode 100644 mechacrypto/Makefile create mode 100644 mechacrypto/cipher.c create mode 100644 mechacrypto/cipher.h create mode 100644 mechacrypto/crc32.c create mode 100644 mechacrypto/crc32.h create mode 100644 mechacrypto/keys.hpp create mode 100644 mechacrypto/sha256.hpp create mode 100644 mechacrypto/util.c create mode 100644 mechacrypto/util.h create mode 100644 mechadump/Makefile create mode 100644 mechadump/common.hpp create mode 100644 mechadump/configexploit.cpp create mode 100644 mechadump/configexploit.hpp create mode 100644 mechadump/dumper.cpp create mode 100644 mechadump/dumper.hpp create mode 100644 mechadump/mechadump.cpp create mode 100644 mechadump/sysinfo.cpp create mode 100644 mechadump/sysinfo.hpp create mode 100644 mechapatchtool/Makefile create mode 100644 mechapatchtool/mechapatchtool.cpp create mode 100644 patch/Makefile create mode 100644 patch/patch-cdprotect-hook.S create mode 100644 patch/patch-irq-hook.S create mode 100644 patch/patch.ld create mode 100644 payload/Makefile create mode 100644 payload/payload-crt0.S create mode 100644 payload/payload-fastdump.c create mode 100644 payload/payload-keystoredump.c create mode 100644 payload/payload-writenvm.c create mode 100644 payload/payload.ld diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6ada6d --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +KEY_DEFINES = -DMECHA_PATCH_KEY=$(MECHA_PATCH_KEY) -DGLOBAL_FLAGS_KEY=$(GLOBAL_FLAGS_KEY) + +PATCHES = patch-irq-hook patch-cdprotect-hook +PATCHES_UNFIXED = $(foreach obj,$(PATCHES),bin/$(obj).unfixed.bin) +PATCHES_FIXED = $(foreach obj,$(PATCHES),bin/$(obj).fixed.bin) +PATCHES_DECRYPTED = $(foreach obj,$(PATCHES),bin/$(obj).decrypted.bin) +PAYLOADS = bin/payload-fastdump.bin bin/payload-writenvm.bin bin/payload-keystoredump.bin + +# Make subcommands so we can repeat them between clean and all more easily. +MAKECMD_MECHADUMP = $(MAKE) TOOL_PREFIX=$(EE_PREFIX) OUT_DIR=../out LIB_DIR=../mechacrypto/lib-ee \ + INCLUDE_DIR=../mechacrypto PATCH_DIR=../bin DEFINES="$(KEY_DEFINES)" -C mechadump +MAKECMD_MECHAPATCHTOOL = $(MAKE) TOOL_PREFIX=$(HOST_PREFIX) OUT_DIR=../bin BIN_DIR=bin-host \ + LIB_DIR=../mechacrypto/lib-host INCLUDE_DIR=../mechacrypto DEFINES="$(KEY_DEFINES)" -C mechapatchtool +MAKECMD_MECHACRYPTO_HOST = $(MAKE) TOOL_PREFIX=$(HOST_PREFIX) OUT_DIR=lib-host BIN_DIR=bin-host \ + -C mechacrypto +MAKECMD_MECHACRYPTO_EE = $(MAKE) TOOL_PREFIX=$(EE_PREFIX) OUT_DIR=lib-ee BIN_DIR=bin-ee -C mechacrypto +MAKECMD_PATCH = $(MAKE) TOOL_PREFIX=$(ARM_PREFIX) OUT_DIR=../bin BIN_DIR=bin-arm -C patch +MAKECMD_PAYLOAD = $(MAKE) TOOL_PREFIX=$(ARM_PREFIX) OUT_DIR=../bin BIN_DIR=bin-arm -C payload + + +.PHONY: all clean debugdata mechadump mechapatchtool libmechacrypto-host libmechacrypto-ee + +all: mechadump libmechacrypto-ee debugdata + +clean: + rm -f bin/mechapatchtool bin/*.bin + $(MAKECMD_MECHADUMP) clean + $(MAKECMD_MECHAPATCHTOOL) clean + $(MAKECMD_MECHACRYPTO_HOST) clean + $(MAKECMD_MECHACRYPTO_EE) clean + $(MAKECMD_PATCH) clean + $(MAKECMD_PAYLOAD) clean + +bin: + mkdir bin +out: + mkdir out + +debugdata: $(PATCHES_DECRYPTED) + +mechadump: $(PATCHES_FIXED) $(PAYLOADS) libmechacrypto-ee | bin out + $(MAKECMD_MECHADUMP) + +$(PATCHES_DECRYPTED): %.decrypted.bin: %.fixed.bin | bin mechapatchtool + bin/mechapatchtool --decrypt --in="$^" --out="$@" +$(PATCHES_FIXED): %.fixed.bin: %.unfixed.bin | bin mechapatchtool + bin/mechapatchtool --fixup --in="$^" --out="$@" +$(PATCHES_UNFIXED): | bin + $(MAKECMD_PATCH) + +$(PAYLOADS): + $(MAKECMD_PAYLOAD) + +mechapatchtool: libmechacrypto-host | bin + $(MAKECMD_MECHAPATCHTOOL) + +libmechacrypto-host: + $(MAKECMD_MECHACRYPTO_HOST) +libmechacrypto-ee: + $(MAKECMD_MECHACRYPTO_EE) diff --git a/mechacrypto/Makefile b/mechacrypto/Makefile new file mode 100644 index 0000000..6f18d3f --- /dev/null +++ b/mechacrypto/Makefile @@ -0,0 +1,42 @@ +BIN_DIR = bin$(OUT_SUFFIX) +ifndef OUT_DIR +OUT_DIR = $(BIN_DIR) +endif + +C_OBJECTS = cipher.o crc32.o util.o +TARGET_C_OBJECTS = $(foreach obj,$(C_OBJECTS),$(BIN_DIR)/$(obj)) + +LIBRARY = $(OUT_DIR)/libmechacrypto$(OUT_SUFFIX).a + +CC = $(TOOL_PREFIX)gcc +CXX = $(TOOL_PREFIX)g++ +AR = $(TOOL_PREFIX)ar + +BASEFLAGS = -O2 -Wall +CCFLAGS = $(BASEFLAGS) -std=c11 +CXXFLAGS = $(BASEFLAGS) -std=c++14 + +.PHONY: all clean headers + +all: $(BIN_DIR) $(OUT_DIR) $(LIBRARY) + +clean: + rm -f $(BIN_DIR)/*.o $(BIN_DIR)/*.a $(LIBRARY) + +$(BIN_DIR): + mkdir $@ +$(OUT_DIR): + mkdir $@ + +$(LIBRARY): $(TARGET_C_OBJECTS) | $(OUT_DIR) + rm -f $@ + $(AR) -crs $@ $^ + +# Will need to fix... +#$(TARGET_C_OBJECTS): $(BIN_DIR)/%.o: %.cpp +# $(CXX) $(CXXFLAGS) -c $^ -o $@ + +$(TARGET_C_OBJECTS): $(BIN_DIR)/%.o: %.c | $(BIN_DIR) headers + $(CC) $(CCFLAGS) -c $^ -o $@ + +headers: *.h diff --git a/mechacrypto/cipher.c b/mechacrypto/cipher.c new file mode 100644 index 0000000..d7a4a72 --- /dev/null +++ b/mechacrypto/cipher.c @@ -0,0 +1,494 @@ +/* + * ps3mca-tool - PlayStation 3 Memory Card Adaptor Software + * Copyright (C) 2011 - jimmikaelkael + * Copyright (C) 2011 - "someone who wants to stay anonymous" + * + * 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 . + */ + +/* + * MechaCon cipher. This is a standard-compliant implementation of DES as + * described in FIPS 46-3. The internal format of the key schedule has been + * changed to allow running the cipher more efficiently on large numbers of + * blocks. + */ + +#include "cipher.h" +#include "util.h" + +/* PC-1 */ +static unsigned char PC1_table[56] = { + 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4, +}; + +/* Left-Shift table */ +static unsigned char LS_table[16] = { + 1, 1, 2, 2, 2, 2, 2, 2, + 1, 2, 2, 2, 2, 2, 2, 1, +}; + +/* + * PC-2x. This one resembles PC-2 from FIPS 46-3 very closely; the only + * difference is that (if line counting starts at 1) odd-numbered lines + * appear before even-numbered lines, but relative order is kept. + */ +static unsigned char PC2x_table[48] = { + 14, 17, 11, 24, 1, 5, + 23, 19, 12, 4, 26, 8, + 41, 52, 31, 37, 47, 55, + 44, 49, 39, 56, 34, 53, + 3, 28, 15, 6, 21, 10, + 16, 7, 27, 20, 13, 2, + 30, 40, 51, 45, 33, 48, + 46, 42, 50, 36, 29, 32, +}; + +/* + * cipherKeyScheduleInner: calculate key schedule of MechaCon cipher. + */ +static void cipherKeyScheduleInner(uint64_t RoundKeys[16], uint64_t Key) +{ + uint64_t Input = 0; + + int i; + for (i = 0; i < 56; i++) { + if (Key & ((uint64_t)1 << (64 - PC1_table[i]))) { + Input |= (uint64_t)1 << (55 - i); + } + } + + uint32_t C = (uint32_t)(Input >> 28) & 0x0FFFFFFF; + uint32_t D = (uint32_t)Input & 0x0FFFFFFF; + + for (i = 0; i < 16; i++) { + /* Left shift. Up to this point, this is a standard DES key schedule */ + if (LS_table[i] == 1) { + C = (C << 1) | (C >> 27); + D = (D << 1) | (D >> 27); + } else { + C = (C << 2) | (C >> 26); + D = (D << 2) | (D >> 26); + } + + uint64_t CD = ((uint64_t)(C & 0x0FFFFFFF) << 28) | (D & 0x0FFFFFFF); + uint64_t Ki = 0; + int j; + for (j = 0; j < 48; j++) { + if (CD & ((uint64_t)1 << (56 - PC2x_table[j]))) { + Ki |= (uint64_t)1 << (63 - 2 - ((j / 6) * 8 + (j % 6))); + } + } + + RoundKeys[i] = Ki; + } +} + +/* IP. */ +static unsigned char IP_table[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, +}; + +/* + * cipherIP: apply MechaCon cipher IP to specified value. + */ +static uint64_t cipherIP(uint64_t Value) +{ + uint64_t Output = 0; + + int i; + for (i = 0; i < 64; i++) { + if (Value & ((uint64_t)1 << (64 - IP_table[i]))) { + Output |= (uint64_t)1 << (63 - i); + } + } + + return Output; +} + + +/* inverse IP. */ +static unsigned char IPinv_table[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25, +}; + +/* + * cipherIPInverse: apply MechaCon cipher inverse IP to specified value. + */ +static uint64_t cipherIPInverse(uint64_t Value) +{ + uint64_t Output = 0; + + int i; + for (i = 0; i < 64; i++) { + if (Value & ((uint64_t)1 << (64 - IPinv_table[i]))) { + Output |= (uint64_t)1 << (63 - i); + } + } + + return Output; +} + + +/* + * DES S+P tables for the fast software implementation. These tables are generated by code in gen.c + * from the official tables described in FIPS 46-3. + * + * All values are reordered to allow simple 6-bit indexing of each table, instead of the bit + * manipulations necessary when using the tables from FIPS 46-3 directly. The values in these + * tables represent the corresponding S box output under the P permutation. To get the output of + * the DES round function, the outputs of all S+P boxes need to be ORed. + */ + +/* S1 */ +static uint32_t SP_box_1[4*16] = { + 0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, + 0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002, + 0x00000202, 0x00800200, 0x00800200, 0x00008200, 0x00008200, 0x00808000, 0x00808000, 0x00800202, + 0x00008002, 0x00800002, 0x00800002, 0x00008002, 0x00000000, 0x00000202, 0x00008202, 0x00800000, + 0x00008000, 0x00808202, 0x00000002, 0x00808000, 0x00808200, 0x00800000, 0x00800000, 0x00000200, + 0x00808002, 0x00008000, 0x00008200, 0x00800002, 0x00000200, 0x00000002, 0x00800202, 0x00008202, + 0x00808202, 0x00008002, 0x00808000, 0x00800202, 0x00800002, 0x00000202, 0x00008202, 0x00808200, + 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002, +}; + +/* S2 */ +static uint32_t SP_box_2[4*16] = { + 0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, + 0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010, + 0x00084000, 0x00080010, 0x40004010, 0x00000000, 0x40000000, 0x00004000, 0x00084010, 0x40080000, + 0x00080010, 0x40000010, 0x00000000, 0x00084000, 0x00004010, 0x40084000, 0x40080000, 0x00004010, + 0x00000000, 0x00084010, 0x40080010, 0x00080000, 0x40004010, 0x40080000, 0x40084000, 0x00004000, + 0x40080000, 0x40004000, 0x00000010, 0x40084010, 0x00084010, 0x00000010, 0x00004000, 0x40000000, + 0x00004010, 0x40084000, 0x00080000, 0x40000010, 0x00080010, 0x40004010, 0x40000010, 0x00080010, + 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000, +}; + +/* S3 */ +static uint32_t SP_box_3[4*16] = { + 0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, + 0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104, + 0x04000000, 0x00000004, 0x04010100, 0x00000100, 0x00010100, 0x04010000, 0x04010004, 0x00010104, + 0x04000104, 0x00010100, 0x00010000, 0x04000104, 0x00000004, 0x04010104, 0x00000100, 0x04000000, + 0x04010100, 0x04000000, 0x00010004, 0x00000104, 0x00010000, 0x04010100, 0x04000100, 0x00000000, + 0x00000100, 0x00010004, 0x04010104, 0x04000100, 0x04000004, 0x00000100, 0x00000000, 0x04010004, + 0x04000104, 0x00010000, 0x04000000, 0x04010104, 0x00000004, 0x00010104, 0x00010100, 0x04000004, + 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100, +}; + +/* S4 */ +static uint32_t SP_box_4[4*16] = { + 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, + 0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000, + 0x80000000, 0x00001000, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x80001000, 0x00001040, + 0x80400040, 0x80000000, 0x00001040, 0x00400040, 0x00001000, 0x00401040, 0x80401040, 0x80000040, + 0x00400040, 0x80400000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00000000, 0x00401000, + 0x00001040, 0x00400040, 0x80400040, 0x80000000, 0x80401000, 0x80001040, 0x80001040, 0x00000040, + 0x80401040, 0x80000040, 0x80000000, 0x00001000, 0x80400000, 0x80001000, 0x00401040, 0x80400040, + 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040, +}; + +/* S5 */ +static uint32_t SP_box_5[4*16] = { + 0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, + 0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000, + 0x01000000, 0x20040000, 0x20040000, 0x00000000, 0x20000080, 0x21040080, 0x21040080, 0x01000080, + 0x21040000, 0x20000080, 0x00000000, 0x21000000, 0x01040080, 0x01000000, 0x21000000, 0x00040080, + 0x00040000, 0x21000080, 0x00000080, 0x01000000, 0x20000000, 0x01040000, 0x21000080, 0x20040080, + 0x01000080, 0x20000000, 0x21040000, 0x01040080, 0x20040080, 0x00000080, 0x01000000, 0x21040000, + 0x21040080, 0x00040080, 0x21000000, 0x21040080, 0x01040000, 0x00000000, 0x20040000, 0x21000000, + 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080, +}; + +/* S6 */ +static uint32_t SP_box_6[4*16] = { + 0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, + 0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008, + 0x00000000, 0x00200008, 0x10002008, 0x00002000, 0x00202000, 0x10002008, 0x00000008, 0x10200008, + 0x10200008, 0x00000000, 0x00202008, 0x10202000, 0x00002008, 0x00202000, 0x10202000, 0x10000000, + 0x10002000, 0x00000008, 0x10200008, 0x00202000, 0x10202008, 0x00200000, 0x00002008, 0x10000008, + 0x00200000, 0x10002000, 0x10000000, 0x00002008, 0x10000008, 0x10202008, 0x00202000, 0x10200000, + 0x00202008, 0x10202000, 0x00000000, 0x10200008, 0x00000008, 0x00002000, 0x10200000, 0x00202008, + 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008, +}; + +/* S7 */ +static uint32_t SP_box_7[4*16] = { + 0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, + 0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401, + 0x02000400, 0x00100401, 0x00100001, 0x02000400, 0x02000001, 0x02100000, 0x02100400, 0x00100001, + 0x02100000, 0x00000400, 0x00000401, 0x02100401, 0x00100400, 0x00000001, 0x02000000, 0x00100400, + 0x02000000, 0x00100400, 0x00100000, 0x02000401, 0x02000401, 0x02100001, 0x02100001, 0x00000001, + 0x00100001, 0x02000000, 0x02000400, 0x00100000, 0x02100400, 0x00000401, 0x00100401, 0x02100400, + 0x00000401, 0x02000001, 0x02100401, 0x02100000, 0x00100400, 0x00000000, 0x00000001, 0x02100401, + 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001, +}; + +/* S8 */ +static uint32_t SP_box_8[4*16] = { + 0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, + 0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020, + 0x08020000, 0x08000020, 0x08000800, 0x00000820, 0x00020800, 0x00020020, 0x08020020, 0x08020800, + 0x00000820, 0x00000000, 0x00000000, 0x08020020, 0x08000020, 0x08000800, 0x00020820, 0x00020000, + 0x00020820, 0x00020000, 0x08020800, 0x00000800, 0x00000020, 0x08020020, 0x00000800, 0x00020820, + 0x08000800, 0x00000020, 0x08000020, 0x08020000, 0x08020020, 0x08000000, 0x00020000, 0x08000820, + 0x00000000, 0x08020820, 0x00020020, 0x08000020, 0x08020000, 0x08000800, 0x08000820, 0x00000000, + 0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800, +}; + + +/* + * cipherForward: run MechaCon cipher in forward direction. + */ +static void cipherForward(uint64_t Value, uint64_t *Result, const uint64_t RoundKeys[16]) +{ + uint64_t Current = cipherIP(Value); + uint32_t Low = (uint32_t)Current; + uint32_t High = (uint32_t)(Current >> 32); + + int i; + for (i = 0; i <= 15; i++) { + uint32_t X = (Low << 29) | (Low >> 3); + uint32_t Y = (Low << 1) | (Low >> 31); + + X ^= (uint32_t)(RoundKeys[i] >> 32); + Y ^= (uint32_t)RoundKeys[i]; + + uint32_t Tab0 = SP_box_1[ (X >> 24) & 0x3F ]; + uint32_t Tab1 = SP_box_2[ (Y >> 24) & 0x3F ]; + uint32_t Tab2 = SP_box_3[ (X >> 16) & 0x3F ]; + uint32_t Tab3 = SP_box_4[ (Y >> 16) & 0x3F ]; + uint32_t Tab4 = SP_box_5[ (X >> 8) & 0x3F ]; + uint32_t Tab5 = SP_box_6[ (Y >> 8) & 0x3F ]; + uint32_t Tab6 = SP_box_7[ (X >> 0) & 0x3F ]; + uint32_t Tab7 = SP_box_8[ (Y >> 0) & 0x3F ]; + + uint32_t Tab = Tab0 | Tab1 | Tab2 | Tab3 | Tab4 | Tab5 | Tab6 | Tab7; + + uint32_t NewLow = High ^ Tab; + High = Low; + Low = NewLow; + } + + *Result = cipherIPInverse(((uint64_t)Low << 32) | (uint64_t)High); +} + +/* + * _cipherKeySchedule: calculate key schedule of MechaCon cipher. + */ +static void _cipherKeySchedule(const uint8_t Key[8], uint64_t RoundKeys[16]) +{ + uint64_t KeyValue = read_be_uint64(Key); + cipherKeyScheduleInner(RoundKeys, KeyValue); +} + +/* + * _cipherKeyScheduleReverse: calculate key schedule of MechaCon cipher in reverse order. + */ +static void _cipherKeyScheduleReverse(const uint8_t Key[8], uint64_t RoundKeys[16]) +{ + uint64_t ForwardRoundKeys[16]; + int i; + uint64_t KeyValue = read_be_uint64(Key); + cipherKeyScheduleInner(ForwardRoundKeys, KeyValue); + + for (i = 0; i < 16; i++) { + RoundKeys[i] = ForwardRoundKeys[15 - i]; + } +} + +/* + * cipherKeySchedule: perform key schedule for multiple invocations of the MechaCon cipher. + * supports up to three keys. + */ +static int cipherKeySchedule(uint64_t *RoundKeys, const uint8_t *Keys, int KeyCount) +{ + if (KeyCount != 1 && KeyCount != 2 && KeyCount != 3) { + return -1; + } + + _cipherKeySchedule(Keys + 0, RoundKeys + 0); + if (KeyCount == 1) { + return 1; + } + + _cipherKeyScheduleReverse(Keys + 8, RoundKeys + 16); + if (KeyCount == 2) { + return 2; + } + + _cipherKeySchedule(Keys + 16, RoundKeys + 32); + return 3; +} + +/* + * cipherKeyScheduleReverse: perform key schedule for multiple invocations of the MechaCon cipher + * in reverse order. Supports up to three keys. + */ +static int cipherKeyScheduleReverse(uint64_t *RoundKeys, const uint8_t *Keys, int KeyCount) +{ + if (KeyCount != 1 && KeyCount != 2 && KeyCount != 3) { + return -1; + } + + _cipherKeyScheduleReverse(Keys + 0, RoundKeys + 0); + if (KeyCount == 1) { + return 1; + } + + _cipherKeySchedule(Keys + 8, RoundKeys + 16); + if (KeyCount == 2) { + return 2; + } + + memcpy(RoundKeys + 32, RoundKeys, sizeof(RoundKeys[0]) * 16); + + _cipherKeyScheduleReverse(Keys + 16, RoundKeys); + return 3; +} + +/* + * cipherSingleBlock: Invoke the MechaCon cipher multiple times on a single data block. + * If KeyCount is set to one, a single invocation is performed. + * Otherwise, the cipher is called three times; if only two keys + * are provided, the first key is used two times. + */ +static void cipherSingleBlock(uint8_t Result[8], const uint8_t Data[8], + const uint64_t *RoundKeys, int KeyCount) +{ + uint64_t Input = read_be_uint64(Data); + + uint64_t Output; + cipherForward(Input, &Output, RoundKeys); + + if (KeyCount != 1) { + cipherForward(Output, &Output, RoundKeys + 16); + + if (KeyCount == 2) { + cipherForward(Output, &Output, RoundKeys); + } else { + cipherForward(Output, &Output, RoundKeys + 32); + } + } + + write_be_uint64(Result, Output); +} + +/* + * cipherCbcEncrypt: encrypt a buffer using multiple invocations of the MechaCon cipher + * in CBC mode. + */ +int cipherCbcEncrypt(uint8_t *Result, const uint8_t *Data, size_t Length, + const uint8_t *Keys, int KeyCount, const uint8_t IV[8]) +{ + uint64_t RoundKeys[16*3]; + uint8_t LastBlock[8]; + size_t i, k; + + if (Length <= 0) { + return -2; + } + + KeyCount = cipherKeySchedule(RoundKeys, Keys, KeyCount); + if (KeyCount < 1) { + return -1; + } + + memcpy(LastBlock, IV, sizeof(LastBlock)); + for (i = 0; Length - i*8 >= 8; i++) { + uint8_t InputBlock[8]; + + memxor(LastBlock, Data + i*8, InputBlock, sizeof(InputBlock)); + cipherSingleBlock(LastBlock, InputBlock, RoundKeys, KeyCount); + memcpy(Result + i*8, LastBlock, sizeof(LastBlock)); + } + + if (Length - i*8 > 0) { + uint8_t BaseBlock[8]; + + cipherSingleBlock(BaseBlock, LastBlock, RoundKeys, KeyCount); + for (k = 0; k < Length - i*8; k++) { + Result[i*8 + k] = BaseBlock[k] ^ Data[i*8 + k]; + } + } + + return 0; +} + +/* + * cipherCbcDecrypt: decrypt a buffer using multiple invocations of the MechaCon cipher + * in CBC mode. + */ +int cipherCbcDecrypt(uint8_t *Result, const uint8_t *Data, size_t Length, + const uint8_t *Keys, int KeyCount, const uint8_t IV[8]) +{ + uint64_t RoundKeys[16*3]; + uint8_t LastBlock[8], TailBlock[8]; + size_t i, k; + + if (Length <= 0) { + return -2; + } + + KeyCount = cipherKeyScheduleReverse(RoundKeys, Keys, KeyCount); + if (KeyCount < 1) { + return -1; + } + + memcpy(LastBlock, IV, sizeof(LastBlock)); + for (i = 0; Length - i*8 >= 8; i++) { + uint8_t OutputBlock[8]; + + memcpy(TailBlock, Data + i*8, sizeof(TailBlock)); + + cipherSingleBlock(OutputBlock, TailBlock, RoundKeys, KeyCount); + memxor(LastBlock, OutputBlock, OutputBlock, sizeof(OutputBlock)); + + memcpy(LastBlock, TailBlock, sizeof(TailBlock)); + memcpy(Result + i*8, OutputBlock, sizeof(LastBlock)); + } + + if (Length - i*8 > 0) { + uint8_t BaseBlock[8]; + + cipherKeySchedule(RoundKeys, Keys, KeyCount); + cipherSingleBlock(BaseBlock, TailBlock, RoundKeys, KeyCount); + for (k = 0; k < Length - i*8; k++) { + Result[i*8 + k] = BaseBlock[k] ^ Data[i*8 + k]; + } + } + + return 0; +} + diff --git a/mechacrypto/cipher.h b/mechacrypto/cipher.h new file mode 100644 index 0000000..c7d1e4e --- /dev/null +++ b/mechacrypto/cipher.h @@ -0,0 +1,41 @@ +/* + * ps3mca-tool - PlayStation 3 Memory Card Adaptor Software + * Copyright (C) 2011 - jimmikaelkael + * Copyright (C) 2011 - "someone who wants to stay anonymous" + * + * 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 . + */ + +#ifndef __CIPHER_H__ +#define __CIPHER_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +int cipherCbcEncrypt(uint8_t *Result, const uint8_t *Data, size_t Length, + const uint8_t *Keys, int KeyCount, const uint8_t IV[8]); +int cipherCbcDecrypt(uint8_t *Result, const uint8_t *Data, size_t Length, + const uint8_t *Keys, int KeyCount, const uint8_t IV[8]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mechacrypto/crc32.c b/mechacrypto/crc32.c new file mode 100644 index 0000000..ec6cef0 --- /dev/null +++ b/mechacrypto/crc32.c @@ -0,0 +1,22 @@ +/* Simple public domain implementation of the standard CRC32 checksum. + * Outputs the checksum for each file given as a command line argument. + * Invalid file names and files that cause errors are silently skipped. + * The program reads from stdin if it is called with no arguments. */ + +#include "crc32.h" + +static uint32_t crc32_for_byte(uint32_t r) { + for(int j = 0; j < 8; ++j) + r = (r & 1? 0: (uint32_t)0xEDB88320L) ^ r >> 1; + return r ^ (uint32_t)0xFF000000L; +} + +uint32_t crc32(const void *data, size_t n_bytes, uint32_t crc) { + static uint32_t table[0x100]; + if(!*table) + for(size_t i = 0; i < 0x100; ++i) + table[i] = crc32_for_byte(i); + for(size_t i = 0; i < n_bytes; ++i) + crc = table[(uint8_t)crc ^ ((uint8_t*)data)[i]] ^ crc >> 8; + return crc; +} diff --git a/mechacrypto/crc32.h b/mechacrypto/crc32.h new file mode 100644 index 0000000..bf0f9a0 --- /dev/null +++ b/mechacrypto/crc32.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +uint32_t crc32(const void* data, size_t n_bytes, uint32_t crc); + + +#ifdef __cplusplus +} +#endif diff --git a/mechacrypto/keys.hpp b/mechacrypto/keys.hpp new file mode 100644 index 0000000..da18eb6 --- /dev/null +++ b/mechacrypto/keys.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include "sha256.hpp" + + +#define STRING_CONCAT_(x, y) x ## y +#define STRING_CONCAT(x, y) STRING_CONCAT_(x, y) + + +template +constexpr std::uint64_t CompileTimeSHA256Part(std::uint64_t data) +{ + sha256_word state[8]{}; + sha256_init(state); + + sha256_word input[16] + { + static_cast(data >> 32), + static_cast(data & 0xFFFFFFFF), + 0x80000000, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, sizeof(std::uint64_t) * CHAR_BIT + }; + + sha256_transform(state, input); + + return (static_cast(state[Part * 2]) << 32) + state[Part * 2 + 1]; +} + +template +struct CompileTimeSHA256 +{ +private: + enum : std::uint64_t + { + ab = CompileTimeSHA256Part<0>(Key), + cd = CompileTimeSHA256Part<1>(Key), + ef = CompileTimeSHA256Part<2>(Key), + gh = CompileTimeSHA256Part<3>(Key), + }; +public: + enum : std::uint32_t + { + a = static_cast(ab >> 32), + b = static_cast(ab & 0xFFFFFFFF), + c = static_cast(cd >> 32), + d = static_cast(cd & 0xFFFFFFFF), + e = static_cast(ef >> 32), + f = static_cast(ef & 0xFFFFFFFF), + g = static_cast(gh >> 32), + h = static_cast(gh & 0xFFFFFFFF), + }; +}; + + +constexpr std::uint64_t g_desParityMask = 0x01010101'01010101; + +constexpr std::uint64_t g_mechaPatchKey = STRING_CONCAT(0x, MECHA_PATCH_KEY) & ~g_desParityMask; +constexpr std::uint64_t g_globalFlagsKey = STRING_CONCAT(0x, GLOBAL_FLAGS_KEY) & ~g_desParityMask; + +static_assert( + CompileTimeSHA256::a == 0x2B69BA04 && + CompileTimeSHA256::b == 0x87A716C4 && + CompileTimeSHA256::c == 0xFF66452A && + CompileTimeSHA256::d == 0x816910B7 && + CompileTimeSHA256::e == 0xB6CD2541 && + CompileTimeSHA256::f == 0x898A24C5 && + CompileTimeSHA256::g == 0x9CADB82D && + CompileTimeSHA256::h == 0xF306EF29, + "incorrect MECHA_PATCH_KEY"); +static_assert( + CompileTimeSHA256::a == 0x720CB04F && + CompileTimeSHA256::b == 0x58712834 && + CompileTimeSHA256::c == 0x9053B65E && + CompileTimeSHA256::d == 0xEDAB6607 && + CompileTimeSHA256::e == 0x85F5989D && + CompileTimeSHA256::f == 0x7788F311 && + CompileTimeSHA256::g == 0xE799B490 && + CompileTimeSHA256::h == 0x3053EABA, + "incorrect GLOBAL_FLAGS_KEY"); + diff --git a/mechacrypto/sha256.hpp b/mechacrypto/sha256.hpp new file mode 100644 index 0000000..244a530 --- /dev/null +++ b/mechacrypto/sha256.hpp @@ -0,0 +1,143 @@ +// This implementation is not very fast; it's meant for a simple +// implementation that can be used for doing SHA-256 at compile time. + +#pragma once + +#include + +using sha256_word = std::uint32_t; + + +inline constexpr sha256_word sha256_shift_left(sha256_word value, unsigned shift) +{ + if (shift >= 32u) + { + return 0u; + } + + // Mask out the high bits to avoid issues with promotion and + // compile-time overflow warnings. + value &= sha256_word(-1) >> shift; + + return static_cast(value << shift); +} + +inline constexpr sha256_word sha256_shift_right(sha256_word value, unsigned shift) +{ + if (shift >= 32u) + { + return 0u; + } + + return static_cast(value >> shift); +} + +inline constexpr sha256_word sha256_rotate_right(sha256_word value, unsigned shift) +{ + shift %= 32u; + + return sha256_shift_left(value, 32u - shift) | sha256_shift_right(value, shift); +} + +inline constexpr sha256_word sha256_ch(sha256_word x, sha256_word y, sha256_word z) +{ + return (x & y) ^ ((~x) & z); +} + +inline constexpr sha256_word sha256_maj(sha256_word x, sha256_word y, sha256_word z) +{ + return (x & y) ^ (x & z) ^ (y & z); +} + +inline constexpr sha256_word sha256_ep0(sha256_word x) +{ + return sha256_rotate_right(x, 2) ^ sha256_rotate_right(x, 13) ^ sha256_rotate_right(x, 22); +} + +inline constexpr sha256_word sha256_ep1(sha256_word x) +{ + return sha256_rotate_right(x, 6) ^ sha256_rotate_right(x, 11) ^ sha256_rotate_right(x, 25); +} + +inline constexpr sha256_word sha256_sigma0(sha256_word x) +{ + return sha256_rotate_right(x, 7) ^ sha256_rotate_right(x, 18) ^ sha256_shift_right(x, 3); +} + +inline constexpr sha256_word sha256_sigma1(sha256_word x) +{ + return sha256_rotate_right(x, 17) ^ sha256_rotate_right(x, 19) ^ sha256_shift_right(x, 10); +} + +// The main implementation +inline constexpr void sha256_transform(sha256_word(&state)[8], const sha256_word(&input)[16]) +{ + constexpr sha256_word k[64] = + { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, + }; + + sha256_word a = state[0]; + sha256_word b = state[1]; + sha256_word c = state[2]; + sha256_word d = state[3]; + sha256_word e = state[4]; + sha256_word f = state[5]; + sha256_word g = state[6]; + sha256_word h = state[7]; + + sha256_word w[64]{}; + + for (int i = 0; i < 16; ++i) + { + w[i] = input[i]; + } + + for (int i = 16; i < 64; ++i) + { + w[i] = sha256_sigma1(w[i - 2]) + w[i - 7] + sha256_sigma0(w[i - 15]) + w[i - 16]; + } + + for (int i = 0; i < 64; ++i) + { + sha256_word temp1 = h + sha256_ep1(e) + sha256_ch(e, f, g) + k[i] + w[i]; + sha256_word temp2 = sha256_ep0(a) + sha256_maj(a, b, c); + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; +} + +inline constexpr void sha256_init(sha256_word(&state)[8]) +{ + state[0] = 0x6A09E667; + state[1] = 0xBB67AE85; + state[2] = 0x3C6EF372; + state[3] = 0xA54FF53A; + state[4] = 0x510E527F; + state[5] = 0x9B05688C; + state[6] = 0x1F83D9AB; + state[7] = 0x5BE0CD19; +} diff --git a/mechacrypto/util.c b/mechacrypto/util.c new file mode 100644 index 0000000..9447255 --- /dev/null +++ b/mechacrypto/util.c @@ -0,0 +1,216 @@ +/* + * ps3mca-tool - PlayStation 3 Memory Card Adaptor Software + * Copyright (C) 2011 - jimmikaelkael + * Copyright (C) 2011 - "someone who wants to stay anonymous" + * + * 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 "util.h" + +void memrcpy(void *dst, void *src, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) { + ((uint8_t *)dst)[i] = ((uint8_t *)src)[len-1-i]; + } +} + +/* + * memxor: perform exclusive-or of memory buffers. + */ +void memxor(const void *a, const void *b, void *Result, size_t Length) +{ + size_t i; + for (i = 0; i < Length; i++) { + ((uint8_t *)Result)[i] = ((uint8_t *)a)[i] ^ ((uint8_t *)b)[i]; + } +} + +/* + * write_le_uint16: write an unsigned 16 bits Little Endian + * value to a buffer + */ +void write_le_uint16(uint8_t *buf, uint16_t val) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); +} + +/* + * write_le_uint32: write an unsigned 32 bits Little Endian + * value to a buffer + */ +void write_le_uint32(uint8_t *buf, uint32_t val) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); +} + +/* + * write_le_uint64: write an unsigned 64 bits Little Endian + * value to a buffer + */ +void write_le_uint64(uint8_t *buf, uint64_t val) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); + buf[4] = (uint8_t)(val >> 32); + buf[5] = (uint8_t)(val >> 40); + buf[6] = (uint8_t)(val >> 48); + buf[7] = (uint8_t)(val >> 56); +} + +/* + * read_le_uint16: read an unsigned 16 bits Little Endian + * value from a buffer + */ +uint16_t read_le_uint16(const uint8_t *buf) +{ + register uint16_t val; + + val = buf[0]; + val |= ((uint16_t)buf[1] << 8); + + return val; +} + +/* + * read_le_uint32: read an unsigned 32 bits Little Endian + * value from a buffer + */ +uint32_t read_le_uint32(const uint8_t *buf) +{ + register uint32_t val; + + val = buf[0]; + val |= ((uint32_t)buf[1] << 8); + val |= ((uint32_t)buf[2] << 16); + val |= ((uint32_t)buf[3] << 24); + + return val; +} + +/* + * read_le_uint64: read an unsigned 64 bits Little Endian + * value from a buffer + */ +uint64_t read_le_uint64(const uint8_t *buf) +{ + register uint64_t val; + + val = buf[0]; + val |= ((uint64_t)buf[1] << 8); + val |= ((uint64_t)buf[2] << 16); + val |= ((uint64_t)buf[3] << 24); + val |= ((uint64_t)buf[4] << 32); + val |= ((uint64_t)buf[5] << 40); + val |= ((uint64_t)buf[6] << 48); + val |= ((uint64_t)buf[7] << 56); + + return val; +} + +/* + * write_be_uint16: write an unsigned 16 bits Big Endian + * value to a buffer + */ +void write_be_uint16(uint8_t* buf, uint16_t val) +{ + buf[0] = (uint8_t)(val >> 8); + buf[1] = (uint8_t)val; +} + +/* + * write_be_uint32: write an unsigned 32 bits Big Endian + * value to a buffer + */ +void write_be_uint32(uint8_t* buf, uint32_t val) +{ + buf[0] = (uint8_t)(val >> 24); + buf[1] = (uint8_t)(val >> 16); + buf[2] = (uint8_t)(val >> 8); + buf[3] = (uint8_t)val; +} + +/* + * write_be_uint64: write an unsigned 64 bits Big Endian + * value to a buffer + */ +void write_be_uint64(uint8_t* buf, uint64_t val) +{ + buf[0] = (uint8_t)(val >> 56); + buf[1] = (uint8_t)(val >> 48); + buf[2] = (uint8_t)(val >> 40); + buf[3] = (uint8_t)(val >> 32); + buf[4] = (uint8_t)(val >> 24); + buf[5] = (uint8_t)(val >> 16); + buf[6] = (uint8_t)(val >> 8); + buf[7] = (uint8_t)val; +} + +/* + * read_be_uint16: read an unsigned 16 bits Big Endian + * value from a buffer + */ +uint16_t read_be_uint16(const uint8_t* buf) +{ + register uint16_t val; + + val = ((uint16_t)buf[0] << 8); + val |= buf[1]; + + return val; +} + +/* + * read_be_uint32: read an unsigned 32 bits Big Endian + * value from a buffer + */ +uint32_t read_be_uint32(const uint8_t* buf) +{ + register uint32_t val; + + val = ((uint32_t)buf[0] << 24); + val |= ((uint32_t)buf[1] << 16); + val |= ((uint32_t)buf[2] << 8); + val |= buf[3]; + + return val; +} + +/* + * read_be_uint64: read an unsigned 64 bits Big Endian + * value from a buffer + */ +uint64_t read_be_uint64(const uint8_t* buf) +{ + register uint64_t val; + + val = ((uint64_t)buf[0] << 56); + val |= ((uint64_t)buf[1] << 48); + val |= ((uint64_t)buf[2] << 40); + val |= ((uint64_t)buf[3] << 32); + val |= ((uint64_t)buf[4] << 24); + val |= ((uint64_t)buf[5] << 16); + val |= ((uint64_t)buf[6] << 8); + val |= buf[7]; + + return val; +} + diff --git a/mechacrypto/util.h b/mechacrypto/util.h new file mode 100644 index 0000000..a5a8736 --- /dev/null +++ b/mechacrypto/util.h @@ -0,0 +1,52 @@ +/* + * ps3mca-tool - PlayStation 3 Memory Card Adaptor Software + * Copyright (C) 2011 - jimmikaelkael + * Copyright (C) 2011 - "someone who wants to stay anonymous" + * + * 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 . + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +void memrcpy(void *dst, void *src, size_t len); +void memxor(const void *a, const void *b, void *Result, size_t Length); +void write_le_uint16(uint8_t *buf, uint16_t val); +void write_le_uint32(uint8_t *buf, uint32_t val); +void write_le_uint64(uint8_t *buf, uint64_t val); +uint16_t read_le_uint16(const uint8_t *buf); +uint32_t read_le_uint32(const uint8_t *buf); +uint64_t read_le_uint64(const uint8_t *buf); +void write_be_uint16(uint8_t *buf, uint16_t val); +void write_be_uint32(uint8_t *buf, uint32_t val); +void write_be_uint64(uint8_t *buf, uint64_t val); +uint16_t read_be_uint16(const uint8_t *buf); +uint32_t read_be_uint32(const uint8_t *buf); +uint64_t read_be_uint64(const uint8_t *buf); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/mechadump/Makefile b/mechadump/Makefile new file mode 100644 index 0000000..e4b3683 --- /dev/null +++ b/mechadump/Makefile @@ -0,0 +1,91 @@ +BIN_DIR = bin$(OUT_SUFFIX) +ifndef OUT_DIR +OUT_DIR = $(BIN_DIR) +endif + +IRXES = fileXio iomanX mcman mcserv mtapman padman sio2man usbd usbhdfsd +IRX_FILES = $(foreach irx,$(IRXES),$(PS2SDK)/iop/irx/$(irx).irx) +IRX_SOURCES = $(foreach irx,$(IRXES),$(BIN_DIR)/$(irx).irx.c) +IRX_OBJECTS = $(foreach irx,$(IRXES),$(BIN_DIR)/$(irx).irx.o) + +PATCHES = patch-irq-hook patch-cdprotect-hook +PAYLOADS = payload-fastdump payload-writenvm payload-keystoredump +PATCH_OBJECTS = $(foreach obj,$(PATCHES),$(BIN_DIR)/$(obj).fixed.o) +PAYLOAD_OBJECTS = $(foreach obj,$(PAYLOADS),$(BIN_DIR)/$(obj).o) +BIN_OBJECTS = $(PATCH_OBJECTS) $(PAYLOAD_OBJECTS) $(IRX_OBJECTS) + +C_OBJECTS = +TARGET_C_OBJECTS = $(foreach obj,$(C_OBJECTS),$(BIN_DIR)/$(obj)) +CXX_OBJECTS = configexploit.o dumper.o mechadump.o sysinfo.o +TARGET_CXX_OBJECTS = $(foreach obj,$(CXX_OBJECTS),$(BIN_DIR)/$(obj)) + +EE_DEFINES = -D_EE -G0 +EE_INCLUDE = -I$(PS2SDK)/ee/include -I$(PS2SDK)/common/include +EE_LDFLAGS = -L$(PS2SDK)/ee/lib -T$(PS2SDK)/ee/startup/linkfile + +EXE = $(OUT_DIR)/mechadump.elf + +LIBS = -lmechacrypto -ldebug -lpadx -lmtap -lmc -lfileXio -lpatches -lc -lcdvd + +CC = $(TOOL_PREFIX)gcc +CXX = $(TOOL_PREFIX)g++ +LD = $(TOOL_PREFIX)g++ +OBJCOPY = $(TOOL_PREFIX)objcopy + +CPPFLAGS = -O2 -Wall $(EE_DEFINES) $(DEFINES) -I$(INCLUDE_DIR) $(EE_INCLUDE) +CFLAGS = $(CPPFLAGS) -std=c11 +CXXFLAGS = $(CPPFLAGS) -std=c++14 +LDFLAGS = $(EE_LDFLAGS) + +.PHONY: all clean headers + +all: $(BIN_DIR) $(OUT_DIR) $(EXE) + +clean: + rm -f $(BIN_DIR)/*.o $(BIN_DIR)/*.c $(EXE) + +$(BIN_DIR): + mkdir $@ +$(OUT_DIR): + mkdir $@ + +$(EXE): $(TARGET_C_OBJECTS) $(TARGET_CXX_OBJECTS) $(BIN_OBJECTS) | $(OUT_DIR) + $(LD) $(LDFLAGS) -L$(LIB_DIR) $^ $(LIBS) -o $@ + +$(TARGET_C_OBJECTS): $(BIN_DIR)/%.o: %.c | headers + $(CC) $(CFLAGS) -c $^ -o $@ + +$(TARGET_CXX_OBJECTS): $(BIN_DIR)/%.o: %.cpp | headers + $(CXX) $(CXXFLAGS) -c $^ -o $@ + +$(IRX_OBJECTS): $(BIN_DIR)/%.irx.o: $(BIN_DIR)/%.irx.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(IRX_SOURCES): $(BIN_DIR)/%.irx.c: $(PS2SDK)/iop/irx/%.irx + bin2c $^ $@ irx_`echo "$^" | sed s/^.*\\\\/// | sed s/\\\\.irx//` + +$(BIN_DIR)/patch-irq-hook.fixed.o: $(BIN_DIR)/patch-irq-hook.fixed.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(BIN_DIR)/patch-irq-hook.fixed.c: $(PATCH_DIR)/patch-irq-hook.fixed.bin + bin2c $^ $@ patch_irq_hook + +$(BIN_DIR)/patch-cdprotect-hook.fixed.o: $(BIN_DIR)/patch-cdprotect-hook.fixed.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(BIN_DIR)/patch-cdprotect-hook.fixed.c: $(PATCH_DIR)/patch-cdprotect-hook.fixed.bin + bin2c $^ $@ patch_cdprotect_hook + +$(BIN_DIR)/payload-fastdump.o: $(BIN_DIR)/payload-fastdump.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(BIN_DIR)/payload-fastdump.c: $(PATCH_DIR)/payload-fastdump.bin + bin2c $^ $@ payload_fastdump + +$(BIN_DIR)/payload-writenvm.o: $(BIN_DIR)/payload-writenvm.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(BIN_DIR)/payload-writenvm.c: $(PATCH_DIR)/payload-writenvm.bin + bin2c $^ $@ payload_writenvm + +$(BIN_DIR)/payload-keystoredump.o: $(BIN_DIR)/payload-keystoredump.c + $(CC) $(CFLAGS) -c $^ -o $@ +$(BIN_DIR)/payload-keystoredump.c: $(PATCH_DIR)/payload-keystoredump.bin + bin2c $^ $@ payload_keystoredump + +headers: *.hpp diff --git a/mechadump/common.hpp b/mechadump/common.hpp new file mode 100644 index 0000000..b3a854a --- /dev/null +++ b/mechadump/common.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +using std::int8_t; +using std::int16_t; +using std::int32_t; +using std::int64_t; +using std::uint8_t; +using std::uint16_t; +using std::uint32_t; +using std::uint64_t; +using std::size_t; +using std::ptrdiff_t; + + +enum : uint32_t +{ + MECHACON_ROM_START = 0x00000000, + MECHACON_ROM_SIZE = 0x44000, + MECHACON_RAM_START = 0x02000000, + MECHACON_RAM_SIZE = 0x4000, + KEYSTORE_SIZE = 0x400, +}; + +class DebugOutput +{ +public: + virtual ~DebugOutput() {} + virtual void VPrintf(const char* format, va_list args) = 0; + + void Printf(const char* format, ...) + { + va_list args; + va_start(args, format); + VPrintf(format, args); + va_end(args); + } +}; diff --git a/mechadump/configexploit.cpp b/mechadump/configexploit.cpp new file mode 100644 index 0000000..f5b604b --- /dev/null +++ b/mechadump/configexploit.cpp @@ -0,0 +1,364 @@ +#include "common.hpp" + +#include +#include +#include + +#include "configexploit.hpp" + +// OpenConfig params: +// 0 = must be 00 or 01; becomes bit 1 of config_open_status +// 1 = written to config_open_status+1 +// 2 = written to config_open_status+3, and times 16 to config_write_size +// config_open_status+1 selects: +// 0 = config_write_buffer1, offset 0x270 +// 1 = config_write_buffer2, offset 0x2B0 +// 2 = config_write_buffer3, offset 0x200 +// +// ReadConfig: +// no params +// reads 16 bytes at config_write_address + value of config_open_status+2 +// subtracts 1 from config_open_status+3 +// adds 16 then mods by 256 to config_open_status+2 + +enum ConfigBlockID : uint8_t +{ + CONFIG_BLOCK_270 = 0x00, + CONFIG_BLOCK_2B0 = 0x01, + CONFIG_BLOCK_200 = 0x02, +}; + +struct ConfigBlockEntry +{ + ConfigBlockID m_blockID; + unsigned m_eepromByteAddress; + size_t m_size; + size_t m_configInfoOffset; +}; +const ConfigBlockEntry s_configBlocks[] = +{ + { CONFIG_BLOCK_270, 0x270, sizeof(ConfigInfo::m_config270), offsetof(ConfigInfo, m_config270) }, + { CONFIG_BLOCK_2B0, 0x2B0, sizeof(ConfigInfo::m_config2B0), offsetof(ConfigInfo, m_config2B0) }, + { CONFIG_BLOCK_200, 0x200, sizeof(ConfigInfo::m_config200), offsetof(ConfigInfo, m_config200) }, +}; + +// Read EEPROM at a given arbitrary offset. +// The offset is given in bytes, though note that the EEPROM device is +// natively addressed in 16-bit words. +bool ReadNVM(uint8_t* output, unsigned offset, unsigned length) +{ + uint8_t result[3]; + + if (offset >= 0x400) + return false; + if (0x400 - offset < length) + return false; + + // ReadNVM takes a big-endian EEPROM offset and returns a big-endian uint16, + // unlike everything else in PS2 and Mechacon. + + // If offset is odd, even it out. + if (offset % 2) + { + uint8_t wordOffset[2] = + { + static_cast((offset / 2) >> 8), + static_cast(offset / 2) + }; + if (!sceCdApplySCmd(0x0A, wordOffset, sizeof(wordOffset), result, sizeof(result))) + return false; + if (result[0] != 0x00) + return false; + + *output++ = result[1]; + ++offset; + --length; + } + + // Read words. + while (length >= 2) + { + uint8_t wordOffset[2] = + { + static_cast((offset / 2) >> 8), + static_cast(offset / 2) + }; + if (!sceCdApplySCmd(0x0A, wordOffset, sizeof(wordOffset), result, sizeof(result))) + return false; + if (result[0] != 0x00) + return false; + + *output++ = result[2]; + *output++ = result[1]; + offset += 2; + length -= 2; + } + + // If there's one byte remaining, handle it. + if (length > 0) + { + uint8_t wordOffset[2] = + { + static_cast((offset / 2) >> 8), + static_cast(offset / 2) + }; + if (!sceCdApplySCmd(0x0A, wordOffset, sizeof(wordOffset), result, sizeof(result))) + return false; + if (result[0] != 0x00) + return false; + + *output++ = result[2]; + ++offset; + --length; + } + + return true; +} + +// Calls OpenConfig with the specified parameters. +bool OpenConfig(bool forWriting, uint8_t whichBlock, uint8_t rowCount) +{ + uint8_t request[3] = + { + static_cast(forWriting), + whichBlock, + rowCount + }; + uint8_t reply[1]; + + if (!sceCdApplySCmd(0x40, request, sizeof(request), reply, sizeof(reply))) + return false; + + return reply[0] == 0x00; +} + +// Reads rowCount * 16 bytes to output. +bool ReadConfig(uint8_t* output, uint8_t rowCount) +{ + for (unsigned row = 0; row < rowCount; ++row) + { + uint8_t reply[0x10]; + if (!sceCdApplySCmd(0x41, nullptr, 0, reply, sizeof(reply))) + return false; + + std::memcpy(&output[row * 0x10], reply, 0x10); + } + + return true; +} + +// Do the WriteConfig command to write to EEPROM. +bool WriteConfig(const uint8_t* data, uint8_t rowCount) +{ + for (unsigned row = 0; row < rowCount; ++row) + { + uint8_t reply[1]; + if (!sceCdApplySCmd(0x42, &data[row * 0x10], 0x10, reply, sizeof(reply))) + return false; + + if (reply[0] != 0x00) + return false; + } + + return true; +} + +uint8_t CloseConfigInternal() +{ + uint8_t reply[1]; + if (!sceCdApplySCmd(0x43, nullptr, 0, reply, sizeof(reply))) + return false; + + return reply[0]; +} + +bool CloseConfigAndWait() +{ + for (;;) + { + uint8_t result = CloseConfigInternal(); + if (result == 0x00) // success + return true; + else if (result == 0x01) // EEPROM operations not finished + continue; + else // some error (config never opened?) + return false; + } +} + +// Is this 16-byte row valid for WriteConfig? +bool IsValidConfigRowChecksum(const uint8_t* row) +{ + unsigned sum = 0; + for (int i = 0; i < 15; ++i) + sum += row[i]; + return row[15] == static_cast(sum); +} + +// Fixes the checksum byte after the 15 data bytes of the row. +void FixConfigRowChecksum(uint8_t* row) +{ + unsigned sum = 0; + for (int i = 0; i < 15; ++i) + sum += row[i]; + row[15] = static_cast(sum); +} + +bool ConfigInfo::HasValidChecksums() const +{ + const unsigned char* infoBytes = reinterpret_cast(this); + + for (const ConfigBlockEntry& block : s_configBlocks) + { + for (unsigned x = 0; x < block.m_size; x += 0x10) + { + if (!IsValidConfigRowChecksum(infoBytes + x)) + return false; + } + } + + return true; +} + +// Read the standard config blocks (EEPROM 0x200-0x31F). +bool ReadAllConfig(ConfigInfo& info) +{ + unsigned char* infoBytes = reinterpret_cast(&info); + + for (const ConfigBlockEntry& block : s_configBlocks) + { + if (!OpenConfig(false, block.m_blockID, block.m_size / 0x10)) + return false; + if (!ReadConfig(infoBytes + block.m_configInfoOffset, block.m_size / 0x10)) + return false; + if (!CloseConfigAndWait()) + return false; + } + + return true; +} + +// Write the standard config blocks (EEPROM 0x200-0x31F). +bool WriteAllConfig(const ConfigInfo& info) +{ + const unsigned char* infoBytes = reinterpret_cast(&info); + + for (const ConfigBlockEntry& block : s_configBlocks) + { + if (!OpenConfig(true, block.m_blockID, block.m_size / 0x10)) + return false; + if (!WriteConfig(infoBytes + block.m_configInfoOffset, block.m_size / 0x10)) + return false; + if (!CloseConfigAndWait()) + return false; + } + + return true; +} + +bool WriteConfigExploit(const uint8_t* patchData, int& errorCode) +{ + errorCode = 0; + + // Dump the existing config blocks for restoration. + ConfigInfo configBackup; + if (!ReadAllConfig(configBackup)) + { + errorCode = 1; + return false; + } + + // Weird stuff will happen if any rows' checksums are bad. + if (!configBackup.HasValidChecksums()) + { + errorCode = 2; + return false; + } + + // Both the WriteConfig SCMD handler and the underlying code to write to + // EEPROM wrap around the buffer after 0x100 bytes because they store the + // index as uint8. But we can still ask it to write up to 0x1F0, which + // it will do. This will trash config memory, but we just saved it! + // + // Do the exploit by wrapping the 0x270 buffer around. + // Using the 0x270 buffer means that there is 0x70+0x40+0x70 = 0x120 + // bytes of memory to hit our target without corrupting critical + // structures that happen to be right after the third buffer. We only + // need 0x100... + // + // The simplest way to do this write is to just imagine that we're + // writing 0x400 - 0x270 = 0x190 bytes. The wrapping buffer just means + // that copies of the patch data end up where config used to be. + if (!OpenConfig(true, CONFIG_BLOCK_270, 0x190 / 0x10)) + { + errorCode = 3; + return false; + } + + // Write zero rows until it's time to send patch data. + uint8_t zeros[0x10]{}; + for (int i = 0; i < 0x190 - 0xE0; i += 0x10) + { + if (!WriteConfig(zeros, 1)) + { + // Attempt to restore config. + CloseConfigAndWait(); + WriteAllConfig(configBackup); + + errorCode = 4; + return false; + } + } + + // Write the 0xE0 bytes of patch data. However, hold off on the last + // row for a safety check. + if (!WriteConfig(patchData, 0xD0 / 0x10)) + { + // Attempt to restore config. + CloseConfigAndWait(); + WriteAllConfig(configBackup); + + errorCode = 5; + return false; + } + + // Write the final row. + if (!WriteConfig(patchData + 0xD0, 0x10 / 0x10)) + { + // Attempt to restore config. + CloseConfigAndWait(); + WriteAllConfig(configBackup); + + errorCode = 6; + return false; + } + + // Need to make sure write finishes... + + sleep(2); + + CloseConfigAndWait(); + + // The write succeeded. Return true no matter what now, but set + // error code if restoring config fails. + if (!WriteAllConfig(configBackup)) + { + errorCode = 7; + return true; + } + + uint8_t checkPatchset[0xE0]; + if (!ReadNVM(checkPatchset, 0x320, 0xE0)) + { + errorCode = 8; + return false; + } + + if (std::memcmp(checkPatchset, patchData, 0xE0) != 0) + { + errorCode = 9; + return false; + } + + return true; +} diff --git a/mechadump/configexploit.hpp b/mechadump/configexploit.hpp new file mode 100644 index 0000000..a5f1748 --- /dev/null +++ b/mechadump/configexploit.hpp @@ -0,0 +1,28 @@ +#pragma once + +// Standard config information (i.e., without us hacking it). +struct ConfigInfo +{ + // block 2: 200-26F + uint8_t m_config200[0x70]; + // block 0: 270-2AF + uint8_t m_config270[0x40]; + // block 1: 2B0-31F + uint8_t m_config2B0[0x70]; + + bool HasValidChecksums() const; +}; + +// Read EEPROM at a given arbitrary offset. +// The offset is given in bytes, though note that the EEPROM device is +// natively addressed in 16-bit words. +bool ReadNVM(uint8_t* output, unsigned offset, unsigned length); + +// Read the standard config blocks (EEPROM 0x200-0x31F). +bool ReadAllConfig(ConfigInfo& info); + +// Does the exploit to write the 0xE0 bytes of patch data. +// WARNING: THIS TRASHES RAM AFTER THE LAST WRITE BUFFER. DO NOT OPERATE +// THE LASER WHILE RAM IS TRASHED THIS WAY. INVALID LASER PARAMETERS COULD +// DAMAGE THE MACHINE. +bool WriteConfigExploit(const uint8_t* patchData, int& errorCode); diff --git a/mechadump/dumper.cpp b/mechadump/dumper.cpp new file mode 100644 index 0000000..e7eb22c --- /dev/null +++ b/mechadump/dumper.cpp @@ -0,0 +1,554 @@ +#include "common.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "dumper.hpp" +#include "util.h" + + +bool IsBackDoorFunctioning() +{ + // Issue 03:A4 with a zero response to the challenge. + // The original code will just return a 0; ours will return A4. + uint8_t request[9]{ 0xA4 }; + uint8_t reply[1]; + if (!sceCdApplySCmd(0x03, request, sizeof(request), reply, sizeof(reply))) + return false; + + return reply[0] == 0xA4; +} + +bool BackDoorReadU32(uint32_t *value, uint32_t address) +{ + uint32_t params[4]; + params[0] = 0x000000A4; + params[1] = 0; + params[2] = address; + params[3] = 0; + + uint8_t result[5]; + memset(result, 0xCC, sizeof(result)); + if (!sceCdApplySCmd(0x03, params, sizeof(params), result, sizeof(result))) + return false; + if (result[0] != 0x81) + return false; + + params[0] = 0x44434DA4; + memset(result, 0xCC, sizeof(result)); + if (!sceCdApplySCmd(0x03, params, 9, result, sizeof(result))) + return false; + if (result[0] != 0x42) + return false; + + memcpy(value, &result[1], sizeof(*value)); + return true; +} + +bool BackDoorExecuteU32(uint8_t *output16, uint32_t address, uint32_t r3) +{ + uint32_t params[4]; + params[0] = 0x000000A4; + params[1] = 1; + params[2] = address; + params[3] = r3; + + uint8_t result[5]; + memset(result, 0xCC, sizeof(result)); + if (!sceCdApplySCmd(0x03, params, sizeof(params), result, sizeof(result))) + return false; + if (result[0] != 0x81) + return false; + + params[0] = 0x44434DA4; + memset(result, 0xCC, sizeof(result)); + if (!sceCdApplySCmd(0x03, params, 9, output16, 16)) + return false; + if (result[0] == 0xA4) + return false; + + return true; +} + +// Dump Mechacon memory slowly using the back door to read 4 bytes at a time. +std::vector DumpMechaconMemory(uint32_t address, uint32_t size, DebugOutput& debug) +{ + std::vector emptyVector; + + if (!IsBackDoorFunctioning()) + { + debug.Printf("Back door not found\n"); + return emptyVector; + } + + if ((size | address) % sizeof(uint32_t)) + { + debug.Printf("DumpMechaconMemory: Misaligned address or size\n"); + return emptyVector; + } + + std::vector result; + result.resize(size / sizeof(uint32_t)); + + for (uint32_t index = 0; index < size / sizeof(uint32_t); ++index) + { + uint32_t target = address + (index * sizeof(uint32_t)); + if (!BackDoorReadU32(&result[index], target)) + { + debug.Printf("Failed to read address %08" PRIX32 "\n", target); + return emptyVector; + } + } + + return result; +} + +std::vector DumpMechaconRAM(DebugOutput& debug) +{ + return DumpMechaconMemory(MECHACON_RAM_START, MECHACON_RAM_SIZE, debug); +} + +bool UploadFakeMagicGateHeader(const void* data, size_t size, DebugOutput& debug) +{ + if (size > 0x7F0) + { + debug.Printf("Size %08X too large for MG header\n", static_cast(size)); + return false; + } + + // Say we're sending a larger header than we actually are, so it doesn't actually start + // processing the header. + uint8_t headerRequest[5] = + { + 0x00, // header code + static_cast((size + 0x10)), + static_cast((size + 0x10) >> 8), + 0x00, // key index? + 0x00, // another key index? + }; + uint8_t reply[1]; + + if (!sceCdApplySCmd(0x90, headerRequest, sizeof(headerRequest), reply, sizeof(reply))) + { + debug.Printf("sceCdApplySCmd failed for command 0x90\n"); + return false; + } + + if (reply[0] != 0x00) + { + debug.Printf("Command 0x90 failed with code %02X\n", reply[0]); + return false; + } + + const uint8_t* ptr = static_cast(data); + while (size > 0) + { + size_t current = (size > 0x10) ? 0x10 : size; + if (!sceCdApplySCmd(0x8D, ptr, current, reply, sizeof(reply))) + { + debug.Printf("sceCdApplySCmd failed for command 0x8D\n"); + return false; + } + + if (reply[0] != 0x00) + { + debug.Printf("Command 0x8D failed with code %02X\n", reply[0]); + return false; + } + + ptr += current; + size -= current; + } + + return true; +} + +bool UploadAndFindCode(const void* data, size_t size, uint32_t& address, DebugOutput& debug) +{ + // Memorize the address and reuse it if possible. + static uint32_t s_previousAddress = uint32_t(-1); + + if (size < 16) + { + debug.Printf("Payload is empty\n"); + return false; + } + if (size % sizeof(uint32_t)) + { + debug.Printf("Size not a multiple of uint32\n"); + return false; + } + + // Flush any existing data. + debug.Printf("Flushing MagicGate header buffer...\n"); + + std::vector zeros; + zeros.resize(0x7F0); + + if (!UploadFakeMagicGateHeader(zeros.data(), zeros.size(), debug)) + { + debug.Printf("Flush failed\n"); + return false; + } + + debug.Printf("Uploading payload to find...\n"); + + // Upload real code. + if (!UploadFakeMagicGateHeader(data, size, debug)) + { + debug.Printf("Upload failed\n"); + return false; + } + + // If we already succeeded before, reuse the address if we can. + if (s_previousAddress != uint32_t(-1)) + { + debug.Printf("Attempting to reuse address %08" PRIX32 "...\n", s_previousAddress); + std::vector check = DumpMechaconMemory(s_previousAddress, size, debug); + if ((check.size() * sizeof(uint32_t) == size) && (std::memcmp(check.data(), data, size) == 0)) + { + debug.Printf("Found again at %08" PRIX32 "\n", s_previousAddress); + address = s_previousAddress; + return true; + } + } + + debug.Printf("Dumping RAM...\n"); + + std::vector ram = DumpMechaconRAM(debug); + if (ram.empty()) + { + debug.Printf("RAM dump failed\n"); + return false; + } + + debug.Printf("Searching for payload...\n"); + + // Find our payload. + uint32_t firstUint32; + std::memcpy(&firstUint32, data, sizeof(firstUint32)); + + uint32_t maxPossible = ram.size() - ((size + sizeof(ram[0]) - 1) / sizeof(ram[0])); + + for (size_t index = 0; index <= maxPossible; ++index) + { + if (ram[index] == firstUint32) + { + if (std::memcmp(ram.data() + index, data, size) == 0) + { + address = MECHACON_RAM_START + (index * sizeof(ram[0])); + debug.Printf("Found uploaded code at %08" PRIX32 "\n", address); + s_previousAddress = address; + return true; + } + } + } + + debug.Printf("Could not find code in RAM\n"); + return false; +} + +void DumpPatchRegistersUsingPayload(const void* payload, size_t size, DebugOutput& debug) +{ + static const uint32_t s_registers[] + { + (0x00 + 0x03880000) | 0x10000000, + (0x00 + 0x03880004), + (0x00 + 0x03880008), + (0x10 + 0x03880000) | 0x10000000, + (0x10 + 0x03880004), + (0x10 + 0x03880008), + (0x20 + 0x03880000) | 0x10000000, + (0x20 + 0x03880004), + (0x20 + 0x03880008), + (0x30 + 0x03880000) | 0x10000000, + (0x30 + 0x03880004), + (0x30 + 0x03880008), + }; + + uint32_t address; + if (!UploadAndFindCode(payload, size, address, debug)) + { + debug.Printf("Could not upload payload\n"); + return; + } + debug.Printf("Found code at %08" PRIX32 "\n", address); + + for (uint32_t target : s_registers) + { + uint8_t reply[16]; + std::memset(reply, 0xCC, sizeof(reply)); + + if (!BackDoorExecuteU32(reply, address | 1, target)) + { + debug.Printf("Could not execute payload for %08" PRIX32 "\n", target); + return; + } + + if (target & 0x10000000) + { + if (reply[0] != 0x6A) + { + debug.Printf("Unexpected result %02X for %08" PRIX32 "\n", reply[0], target); + return; + } + + debug.Printf("%08" PRIX32 " = %02X\n", target, reply[1]); + } + else + { + if (reply[0] != 0x6B) + { + debug.Printf("Unexpected result %02X for %08" PRIX32 "\n", reply[0], target); + return; + } + + debug.Printf("%08" PRIX32 " = %02X%02X%02X%02X\n", target, + reply[4], reply[3], reply[2], reply[1]); + } + } +} + +// Convenient subroute for dumping ROM. +bool Dump14Bytes(uint8_t* output, uint32_t romAddress, uint32_t payloadAddress, DebugOutput& debug) +{ + // Ask for data. + uint8_t reply[16]; + if (!BackDoorExecuteU32(reply, payloadAddress, romAddress)) + { + debug.Printf("Dump14Bytes: BackDoorExecuteU32 failed at %08" PRIX32 "\n", romAddress); + return false; + } + + if (reply[0] != 0x69) + { + debug.Printf("Dump14Bytes: Bad reply %02X from function\n", reply[0]); + return false; + } + + // The checksum includes the ROM address we're reading. + unsigned checksum = 0; + for (unsigned i = 0; i < sizeof(uint32_t); ++i) + { + checksum += static_cast(romAddress >> (i * 8)); + } + + // Checksum the data. + for (unsigned i = 0; i < 14; ++i) + { + checksum += reply[i + 2]; + } + + checksum = static_cast(~checksum); + + // Verify checksum. + if (reply[1] != checksum) + { + debug.Printf("Dump14Bytes: Invalid checksum %02X (correct=%02X) at %08" PRIX32 "\n", + reply[1], checksum, romAddress); + return false; + } + + // Success; copy and return. + std::memcpy(output, &reply[2], 14); + return true; +} + + +std::vector DumpMechaconROMFastWithPayload(const void* payload, size_t payloadSize, + std::function callback, DebugOutput& debug) +{ + std::vector emptyVector; + + debug.Printf("Fastdump: Starting payload upload\n"); + + if (!IsBackDoorFunctioning()) + { + debug.Printf("Fastdump: No back door found?\n"); + return emptyVector; + } + + uint32_t payloadAddress; + if (!UploadAndFindCode(payload, payloadSize, payloadAddress, debug)) + { + debug.Printf("Fastdump: UploadAndFindCode failed\n"); + return emptyVector; + } + payloadAddress |= 1; + + std::vector rom; + rom.resize(MECHACON_ROM_SIZE); + static_assert(MECHACON_ROM_SIZE > 14u, "bad MECHACON_ROM_SIZE constant"); + + debug.Printf("Fastdump: Starting ROM dump\n"); + + // Dump 14 bytes at a time. + constexpr uint32_t mechaconEnd = MECHACON_ROM_START + MECHACON_ROM_SIZE; + + uint32_t prevPage = uint32_t(-1); + uint32_t address; + for (address = MECHACON_ROM_START; mechaconEnd - address >= 14; address += 14) + { + uint32_t atPage = address & ~uint32_t(0xFFF); + if (atPage != prevPage) + { + prevPage = atPage; + debug.Printf("Fastdump: Dump at %08" PRIX32 " so far\n", atPage); + if (!callback(atPage - MECHACON_ROM_START, MECHACON_ROM_SIZE)) + { + debug.Printf("Cancelled.\n"); + return emptyVector; + } + } + + if (!Dump14Bytes(rom.data() + address - MECHACON_ROM_START, address, payloadAddress, debug)) + { + debug.Printf("Fastdump: Failed at %08" PRIX32 "\n", address); + return emptyVector; + } + } + + // Handle the last part. + if (mechaconEnd > address) + { + uint8_t lastBlock[14]; + if (!Dump14Bytes(lastBlock, mechaconEnd - 14, payloadAddress, debug)) + { + debug.Printf("Fastdump: Failed dumping last bytes\n"); + return emptyVector; + } + + std::memcpy(rom.data() + address - MECHACON_ROM_START, + &lastBlock[14 - (mechaconEnd - address)], + mechaconEnd - address); + } + + debug.Printf("Fastdump: Success\n"); + callback(MECHACON_ROM_SIZE, MECHACON_ROM_SIZE); + return rom; +} + + +std::vector DumpMechaconKeystoreWithPayload(const void* payload, size_t payloadSize, + DebugOutput& debug) +{ + std::vector emptyVector; + + debug.Printf("Keystoredump: Starting payload upload\n"); + + if (!IsBackDoorFunctioning()) + { + debug.Printf("Keystoredump: No back door found?\n"); + return emptyVector; + } + + uint32_t payloadAddress; + if (!UploadAndFindCode(payload, payloadSize, payloadAddress, debug)) + { + debug.Printf("Keystoredump: UploadAndFindCode failed\n"); + return emptyVector; + } + payloadAddress |= 1; + + std::vector keystore; + keystore.resize(KEYSTORE_SIZE); + + debug.Printf("Keystoredump: Starting keystore dump\n"); + + // Dump 8 bytes (one "key") at a time. + for (uint32_t keyHalfwordOffset = 0; keyHalfwordOffset < KEYSTORE_SIZE / 2; keyHalfwordOffset += 4) + { + uint8_t reply[16]; + if (!BackDoorExecuteU32(reply, payloadAddress, keyHalfwordOffset)) + { + debug.Printf("Could not execute payload for %04" PRIX32 "\n", keyHalfwordOffset); + return emptyVector; + } + + if (reply[0] != 0x00) + { + debug.Printf("Keystoredump: Payload failed\n"); + return emptyVector; + } + + std::memcpy(keystore.data() + (keyHalfwordOffset * 2), &reply[1], 8); + } + + debug.Printf("Keystoredump: Success\n"); + return keystore; +} + + +bool RestoreNVMConfigAndPatchData(const void* payload, size_t payloadSize, const uint8_t* configData, + DebugOutput& debug) +{ + debug.Printf("RestoreNVMConfigAndPatchData: Starting payload upload\n"); + + if (!IsBackDoorFunctioning()) + { + debug.Printf("RestoreNVMConfigAndPatchData: No back door found?\n"); + return false; + } + + uint32_t payloadAddress; + if (!UploadAndFindCode(payload, payloadSize, payloadAddress, debug)) + { + debug.Printf("RestoreNVMConfigAndPatchData: UploadAndFindCode failed\n"); + return false; + } + payloadAddress |= 1; + + for (uint16_t wordOffset = 0x200 / 2; wordOffset < 0x400 / 2; ++wordOffset) + { + Retry: + uint8_t reply[16]{}; + + // WriteNVM uses big-endian for who knows what reason. + unsigned char command[4]; + write_be_uint16(&command[0], wordOffset); + command[2] = configData[wordOffset * 2 + 1 - 0x200]; + command[3] = configData[wordOffset * 2 + 0 - 0x200]; + uint32_t r3 = read_le_uint32(command); + + if (!BackDoorExecuteU32(reply, payloadAddress, r3)) + { + debug.Printf("Could not execute payload for %04" PRIX16 "\n", wordOffset); + return false; + } + + // Result 1 indicates busy. + if (reply[0] == 0x01) + goto Retry; + + // Other indicates error. + if (reply[0] != 0x00) + { + debug.Printf("RestoreNVMConfigAndPatchData: failed at %04" PRIX16 " with code %02X\n", + wordOffset, reply[0]); + return false; + } + } + + debug.Printf("RestoreNVMConfigAndPatchData: succeeded, in theory?\n"); + return true; +} + + +// Does not return if successful. +bool ResetMechaconAndPowerOff() +{ + if (!IsBackDoorFunctioning()) + return false; + + uint8_t reply[16]{}; + if (!BackDoorExecuteU32(reply, 0, 0)) + return false; + + for (;;) + sleep(1); +} diff --git a/mechadump/dumper.hpp b/mechadump/dumper.hpp new file mode 100644 index 0000000..3e56b3d --- /dev/null +++ b/mechadump/dumper.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +typedef bool DumpMechaconCallback(size_t dumped, size_t total); + +bool IsBackDoorFunctioning(); +std::vector DumpMechaconROMFastWithPayload(const void* payload, size_t payloadSize, + std::function callback, DebugOutput& debug); +std::vector DumpMechaconKeystoreWithPayload(const void* payload, size_t payloadSize, + DebugOutput& debug); +bool RestoreNVMConfigAndPatchData(const void* payload, size_t payloadSize, const uint8_t* configData, + DebugOutput& debug); +bool ResetMechaconAndPowerOff(); diff --git a/mechadump/mechadump.cpp b/mechadump/mechadump.cpp new file mode 100644 index 0000000..e44d92c --- /dev/null +++ b/mechadump/mechadump.cpp @@ -0,0 +1,1046 @@ +#include "common.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "configexploit.hpp" +#include "dumper.hpp" +#include "sysinfo.hpp" + +#include "crc32.h" + + +constexpr int c_versionMajor = 1; +constexpr int c_versionMinor = 0; + + +extern "C" unsigned int size_irx_fileXio; +extern "C" unsigned char irx_fileXio[]; +extern "C" unsigned int size_irx_padman; +extern "C" unsigned char irx_padman[]; +extern "C" unsigned int size_irx_sio2man; +extern "C" unsigned char irx_sio2man[]; +extern "C" unsigned int size_irx_iomanX; +extern "C" unsigned char irx_iomanX[]; +extern "C" unsigned int size_irx_mtapman; +extern "C" unsigned char irx_mtapman[]; +extern "C" unsigned int size_irx_mcman; +extern "C" unsigned char irx_mcman[]; +extern "C" unsigned int size_irx_mcserv; +extern "C" unsigned char irx_mcserv[]; +extern "C" unsigned int size_irx_usbd; +extern "C" unsigned char irx_usbd[]; +extern "C" unsigned int size_irx_usbhdfsd; +extern "C" unsigned char irx_usbhdfsd[]; + +extern "C" unsigned int size_patch_irq_hook; +extern "C" unsigned char patch_irq_hook[]; +extern "C" unsigned int size_patch_cdprotect_hook; +extern "C" unsigned char patch_cdprotect_hook[]; + +extern "C" unsigned int size_payload_fastdump; +extern "C" unsigned char payload_fastdump[]; +extern "C" unsigned int size_payload_writenvm; +extern "C" unsigned char payload_writenvm[]; +extern "C" unsigned int size_payload_keystoredump; +extern "C" unsigned char payload_keystoredump[]; + +const std::vector patch_empty_vector = MakeEmptyPatchset(); + + +enum NvmState +{ + NVM_STATE_EMPTY, + NVM_STATE_OTHER, + NVM_STATE_IRQ_HOOK, + NVM_STATE_CDPROTECT_HOOK, +}; + +alignas(64) char g_pad0Buffer[256]; + +alignas(16) uint8_t g_eeprom[0x400]; + +int g_mechaconMajor = -1; +int g_mechaconMinor = -1; +bool g_systemIsDEX = false; +std::string g_refreshDate; +std::string g_modelString; + + +class SimpleDebugOutput : public DebugOutput +{ +public: + virtual void VPrintf(const char* format, va_list args) + { + std::vprintf(format, args); + std::fflush(stdout); + } +}; + + +// HACK: Hide the silly cursor the debug drawing does. +extern "C" uint8_t msx[]; +void HackToHideCursor() +{ + std::memset(&msx[219 * 8], 0, 8); +} + + +void ResetEverything(bool isPS2Link) +{ + if (!isPS2Link) + { + // Initialize connection to IOP. + SifInitRpc(0); + + // Reset the IOP with the default ROM modules. + while (!SifIopReset("", 0)) + ; + while (!SifIopSync()) + ; + SifInitRpc(0); + + // Patch the kernel as needed. + sbv_patch_enable_lmb(); + sbv_patch_disable_prefix_check(); + sbv_patch_fileio(); + + // Prepare to load modules. + SifLoadFileInit(); + } + + struct IopModule + { + bool m_loadWhenPS2Link; + const void* m_nameOrPointer; + const unsigned int* m_size; + }; + static const IopModule s_iopModules[] = + { + { false, "rom0:CDVDMAN", nullptr }, + { false, irx_iomanX, &size_irx_iomanX }, + { false, irx_fileXio, &size_irx_fileXio }, + { true, irx_sio2man, &size_irx_sio2man }, + { true, irx_mtapman, &size_irx_mtapman }, + { true, irx_padman, &size_irx_padman }, + { true, irx_mcman, &size_irx_mcman }, + { true, irx_mcserv, &size_irx_mcserv }, + { true, irx_usbd, &size_irx_usbd }, + { true, irx_usbhdfsd, &size_irx_usbhdfsd }, + }; + + for (const IopModule& module : s_iopModules) + { + if (isPS2Link && !module.m_loadWhenPS2Link) + continue; + if (module.m_size) + { + SifExecModuleBuffer(const_cast(module.m_nameOrPointer), *module.m_size, 0, nullptr, + nullptr); + } + else + { + SifLoadModule(static_cast(module.m_nameOrPointer), 0, nullptr); + } + } +} + + +bool GetPressedButtons(int& buttons) +{ + if (padGetState(0, 0) != PAD_STATE_STABLE) + return false; + + struct padButtonStatus status; + if (!padRead(0, 0, &status)) + return false; + + buttons = status.btns ^ 0xFFFF; + return true; +} + + +// Wait for either X or O. +void WaitForXorOConfirm() +{ + int buttons; + for (;;) + { + if (!GetPressedButtons(buttons)) + continue; + if (!(buttons & (PAD_CIRCLE | PAD_CROSS))) + break; + } + + for (;;) + { + if (!GetPressedButtons(buttons)) + continue; + if (buttons & (PAD_CIRCLE | PAD_CROSS)) + break; + } +} + + +void WarningScreen() +{ + scr_setbgcolor(0xFF000080); + scr_clear(); + + scr_setXY(0, 5); + scr_printf(" MechaDump v%d.%02d\n", c_versionMajor, c_versionMinor); + scr_printf(" Dumps Dragon-series Mechacon chips.\n"); + scr_printf(" \n"); + scr_printf(" *** WARNING ***\n"); + scr_printf(" THIS PROGRAM IS DANGEROUS, AND HAS A GOOD CHANCE OF\n"); + scr_printf(" BRICKING YOUR PS2, REQUIRING SOLDERING TO RECOVER.\n"); + scr_printf(" \n"); + scr_printf(" USE AT YOUR OWN RISK.\n"); + + sleep(10); + + scr_printf(" \n"); + scr_printf(" Press X or O to continue.\n"); + + WaitForXorOConfirm(); +} + + +std::string RegionFlagsToString(uint32_t regionCode) +{ + static const struct + { + int m_bit; + const char* m_string; + } s_bits[] = { + { 20, "Internal" }, + { 19, "Prototype" }, + { 18, "Arcade" }, + { 17, "QA" }, + { 16, "DEX" }, + { 7, "LatinAmerica" }, + { 6, "China" }, + { 5, "Russia" }, + { 4, "Asia" }, + { 3, "AustraliaNZ" }, + { 2, "Europe" }, + { 1, "NorthAmerica" }, + { 0, "Japan" }, + }; + + std::string response; + for (auto&& entry : s_bits) + { + if (regionCode & (uint32_t(1) << entry.m_bit)) + { + if (!response.empty()) + response.append(" "); + response.append(entry.m_string); + } + } + return response; +} + + +NvmState GetNVMState() +{ + if (std::memcmp(&g_eeprom[0x320], patch_irq_hook, 0xE0) == 0) + return NVM_STATE_IRQ_HOOK; + else if (std::memcmp(&g_eeprom[0x320], patch_cdprotect_hook, 0xE0) == 0) + return NVM_STATE_CDPROTECT_HOOK; + else if (std::memcmp(&g_eeprom[0x320], patch_empty_vector.data(), 0xE0) == 0) + return NVM_STATE_EMPTY; + else + return NVM_STATE_OTHER; +} + + +bool CheckAndWaitForBackdoor() +{ + if (!IsBackDoorFunctioning()) + { + NvmState nvmState = GetNVMState(); + if (nvmState == NVM_STATE_CDPROTECT_HOOK) + { + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(0, 5); + scr_printf(" Back door not yet active. Insert a legitimate disk\n"); + scr_printf(" for this PS2's region to activate it.\n"); + scr_printf(" Hold Triangle to cancel.\n"); + + for (;;) + { + int buttons = 0; + if (GetPressedButtons(buttons) && (buttons & PAD_TRIANGLE)) + return false; + + if (IsBackDoorFunctioning()) + break; + + sleep(1); + } + + sceCdStop(); + sceCdSync(0); + } + else if (nvmState == NVM_STATE_IRQ_HOOK) + { + scr_setbgcolor(0xFF000080); + scr_clear(); + scr_setXY(0, 5); + scr_printf(" Back door not working for some reason!\n"); + scr_printf(" Press X or O to return to the main menu.\n"); + WaitForXorOConfirm(); + return false; + } + } + return true; +} + + +bool SysinfoScreen() +{ + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(0, 5); + + scr_printf(" Retrieving system information...\n"); + + // Read EEPROM. + if (!ReadNVM(g_eeprom, 0, sizeof(g_eeprom))) + { + scr_setbgcolor(0xFF0000FF); + scr_printf(" Could not dump EEPROM. Cannot proceed.\n"); + return false; + } + + // Model string...simple stuff + g_modelString = GetModelString(); + scr_printf(" Model: %s\n", g_modelString.c_str()); + + // Mechacon version stuff. + if (!GetMechaconVersion(g_mechaconMajor, g_mechaconMinor, g_refreshDate)) + { + scr_printf(" Could not retrieve Mechacon version!\n"); + return false; + } + + g_systemIsDEX = static_cast(g_mechaconMinor & 1); + g_mechaconMinor &= ~1; + + if (g_mechaconMajor < 5) + { + scr_setbgcolor(0xFF000080); + scr_printf(" This PS2 is incompatible with this program.\n"); + scr_printf(" This program is only for consoles with 'Dragon' Mechacons:\n"); + scr_printf(" the 50000, 70000, 75000, 77000, 90000, PSX and Bravia series.\n"); + return false; + } + + scr_printf(" Mechacon version: %d.%02d %s\n", g_mechaconMajor, g_mechaconMinor, + g_systemIsDEX ? "DEX" : "CEX"); + scr_printf(" Mechacon build date: %s\n", g_refreshDate.c_str()); + + // Decode the region. + uint32_t regionFlags; + if (DecodeRegionFlags(g_eeprom, regionFlags)) + scr_printf(" Region code: %08" PRIX32 " %s\n", regionFlags, RegionFlagsToString( + regionFlags).c_str()); + else + scr_printf(" Region code: UNKNOWN\n"); + + // Compute patch data CRC. + uint32_t patchCRC = crc32(&g_eeprom[0x320], 0xE0, 0); + scr_printf(" Patchset CRC32: %08" PRIX32 "\n", patchCRC); + + scr_printf(" \n"); + scr_printf(" This system is compatible.\n"); + scr_printf(" Press X or O to continue.\n"); + + WaitForXorOConfirm(); + + return true; +} + + +std::string MakeDumpFilename() +{ + std::string result; + result.reserve(64); + result.append("mechacon-"); + + char versionPart[20]; + std::snprintf(versionPart, sizeof(versionPart), "%d.%02d", g_mechaconMajor, g_mechaconMinor); + result.append(versionPart); + + result.append("-"); + for (char ch : g_refreshDate) + { + if (ch == ' ') + ch = '-'; + if ((ch < '0') || (ch > '9')) + ch = '_'; + result.push_back(ch); + } + + result.append(".bin"); + return result; +} + + +struct MenuOption +{ + const char* m_text; + bool m_enabled; +}; + + +void DrawMenuOption(int x, int y, const MenuOption& option, bool highlighted) +{ + if (!option.m_enabled) + scr_setbgcolor(0xFF404040); + else if (highlighted) + scr_setbgcolor(0xFF000080); + else + scr_setbgcolor(0xFF800000); + + const char* prefix = " "; + if (highlighted) + prefix = "-> "; + + scr_setXY(x, y); + scr_printf("%s%s", prefix, option.m_text); +} + + +size_t ShowMenu(int x, int y, const std::vector& options) +{ + // Find first enabled option. + size_t currentOption = options.size(); + for (size_t i = 0; i < options.size(); ++i) + { + if (options[i].m_enabled) + { + currentOption = i; + break; + } + } + assert(currentOption < options.size()); + + // Any buttons pressed at menu start are ignored until released. + int ignoreButtons = 0; + GetPressedButtons(ignoreButtons); + + // Draw initial menu. + for (size_t i = 0; i < options.size(); ++i) + { + DrawMenuOption(x, y + static_cast(i), options[i], currentOption == i); + } + + // Main loop. + for (;;) + { + // Wait for button presses. + int buttons = 0; + for (;;) + { + if (GetPressedButtons(buttons)) + { + // Un-ignore any buttons that are now released. + ignoreButtons &= buttons; + + // Ignore any buttons that were held at menu start. + buttons &= ~ignoreButtons; + + break; + } + } + + bool changed = false; + + // Ignore up+down together. + if ((buttons & (PAD_UP | PAD_DOWN)) == (PAD_UP | PAD_DOWN)) + buttons &= ~(PAD_UP | PAD_DOWN); + + if (buttons & PAD_UP) + { + for (;;) + { + if (currentOption > 0) + --currentOption; + else + currentOption = options.size() - 1; + if (options[currentOption].m_enabled) + break; + } + ignoreButtons |= PAD_UP; + changed = true; + } + else if (buttons & PAD_DOWN) + { + for (;;) + { + ++currentOption; + if (currentOption >= options.size()) + currentOption = 0; + if (options[currentOption].m_enabled) + break; + } + ignoreButtons |= PAD_DOWN; + changed = true; + } + + // Avoids flicker from constant non-vsync redraw. + if (!changed && !(buttons & (PAD_CIRCLE | PAD_CROSS))) + continue; + + // Update menu. + for (size_t i = 0; i < options.size(); ++i) + { + DrawMenuOption(x, y + static_cast(i), options[i], currentOption == i); + } + + // If selected, return. + if (buttons & (PAD_CIRCLE | PAD_CROSS)) + { + scr_setbgcolor(0xFF800000); + return currentOption; + } + } +} + + +bool Stage_BackupNVM() +{ + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("EEPROM Backup"); + scr_setXY(10, 7); + scr_printf("Are you sure you want to back up your EEPROM to USB? This will"); + scr_setXY(10, 8); + scr_printf("overwrite any previous backup on your USB stick."); + + std::vector menuOptions; + menuOptions.emplace_back(MenuOption{ "NO", true }); + menuOptions.emplace_back(MenuOption{ "YES", true }); + + if (ShowMenu(10, 11, menuOptions) != 1) + return false; + + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(10, 5); + scr_printf("Backing up EEPROM to USB stick...\n"); + + bool success = false; + std::FILE* file = std::fopen("mass:/mechadump_eeprom_backup.bin", "wb"); + if (file) + { + success = std::fwrite(g_eeprom, sizeof(g_eeprom), 1, file) == 1; + std::fclose(file); + } + + if (!success) + { + scr_setXY(10, 7); + scr_setbgcolor(0xFF000080); + scr_printf("BACKUP FAILED "); + scr_setbgcolor(0xFF800000); + scr_printf("- no USB stick inserted?\n"); + scr_setXY(10, 8); + scr_printf("Press X or O to continue.\n"); + WaitForXorOConfirm(); + return false; + } + + scr_setXY(10, 7); + scr_printf("Backup successful.\n"); + scr_setXY(10, 8); + scr_printf("Press X or O to continue.\n"); + WaitForXorOConfirm(); + return true; +} + + +// Does not return if successful. +bool Stage_RestoreNVM() +{ + SimpleDebugOutput debug; + + if (!CheckAndWaitForBackdoor()) + return false; + + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("EEPROM Backup"); + scr_setXY(10, 7); + scr_printf("Are you sure you want to restore up your EEPROM from USB?"); + + std::vector menuOptions; + menuOptions.emplace_back(MenuOption{ "NO", true }); + menuOptions.emplace_back(MenuOption{ "YES", true }); + + if (ShowMenu(10, 11, menuOptions) != 1) + return false; + + scr_setXY(10, 5); + scr_printf("Restoring EEPROM patch data from USB stick...\n"); + + uint8_t eepromConfigToRestore[0x200]; + bool success = false; + std::FILE* file = std::fopen("mass:/mechadump_eeprom_backup.bin", "rb"); + if (file) + { + std::fseek(file, 0x200, SEEK_SET); + success = std::fread(eepromConfigToRestore, 0x200, 1, file) == 1; + std::fclose(file); + } + + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(10, 5); + scr_printf("Restoring EEPROM from backup...\n"); + + if (!success) + { + scr_setXY(10, 7); + scr_setbgcolor(0xFF000080); + scr_printf("RESTORE FAILED "); + scr_setbgcolor(0xFF800000); + scr_printf("- no USB stick inserted?\n"); + scr_setXY(10, 8); + scr_printf("Press X or O to continue.\n"); + WaitForXorOConfirm(); + return false; + } + + if (!RestoreNVMConfigAndPatchData(payload_writenvm, size_payload_writenvm, eepromConfigToRestore, + debug)) + { + scr_setXY(10, 7); + scr_setbgcolor(0xFF000080); + scr_printf("RESTORE FAILED "); + scr_setbgcolor(0xFF800000); + scr_printf("THIS IS REALLY BAD.\n"); + scr_setXY(10, 8); + scr_printf("Press X or O to continue.\n"); + WaitForXorOConfirm(); + return false; + } + + scr_setXY(10, 7); + scr_printf("Restore successful.\n"); + scr_setXY(10, 8); + scr_printf("Press X or O to power off system.\n"); + WaitForXorOConfirm(); + + ResetMechaconAndPowerOff(); + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(10, 7); + scr_setbgcolor(0xFF000080); + scr_printf("Reboot failed "); + scr_setbgcolor(0xFF800000); + scr_printf("Unplug your system, wait for the red light to turn off, then plug in.\n"); + for (;;) + sleep(1); +} + + +// Does not return if successful. Returns on failure. +void Stage_InstallHackChosen(NvmState which) +{ + const unsigned char* patch; + switch (which) + { + case NVM_STATE_IRQ_HOOK: + patch = patch_irq_hook; + break; + case NVM_STATE_CDPROTECT_HOOK: + patch = patch_cdprotect_hook; + break; + default: + assert(false); + ExecOSD(0, nullptr); + for (;;) + sleep(1); + } + + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("Installing back door...\n"); + + int errorCode = -1; + bool result = WriteConfigExploit(patch, errorCode); + + if (result) + { + scr_setXY(10, 8); + scr_printf("SUCCESS! Back door installed.\n"); + scr_setXY(10, 10); + scr_printf("Your system is now frozen.\n"); + scr_setXY(10, 11); + if (g_mechaconMajor < 6) + scr_printf("Please UNPLUG or TURN POWER SUPPLY SWITCH OFF,\n"); + else + scr_printf("Please UNPLUG your PS2, then\n"); + scr_setXY(10, 12); + scr_printf("wait for red light to go out, then power on\n"); + scr_setXY(10, 13); + scr_printf("and run this program again.\n"); + for (;;) + sleep(1); + } + + scr_setbgcolor(0xFF000080); + scr_setXY(10, 8); + scr_printf("FAILED TO INSTALL! "); + scr_setXY(10, 9); + scr_printf("Error code: %d ", errorCode); + scr_setXY(10, 11); + scr_printf("Press X or O to return to the menu."); + WaitForXorOConfirm(); +} + + +// Does not return if successful. Returns on failure. +void Stage_InstallHack() +{ + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("Install Backdoor"); + scr_setXY(10, 7); + scr_printf("Which backdoor payload do you want to use?"); + scr_setXY(10, 8); + + std::vector menuOptions; + menuOptions.emplace_back(MenuOption{ "Cancel", true }); + menuOptions.emplace_back(MenuOption{ "CD Protect Hook (safer, but requires a legitimate game disc)", + true }); + menuOptions.emplace_back(MenuOption{ "IRQ Hook (riskier, but doesn't need drive at all)", true }); + + size_t choice = ShowMenu(10, 11, menuOptions); + NvmState whichHack = NVM_STATE_EMPTY; + switch (choice) + { + case 0: + return; + case 1: + whichHack = NVM_STATE_CDPROTECT_HOOK; + break; + case 2: + whichHack = NVM_STATE_IRQ_HOOK; + break; + default: + assert(false); + return; + } + + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("Install Backdoor"); + scr_setXY(10, 7); + scr_printf("Are you sure you want to install the backdoor?"); + scr_setXY(10, 8); + scr_setbgcolor(0xFF000080); + scr_printf("IT MIGHT BRICK YOUR SYSTEM!"); + scr_setbgcolor(0xFF800000); + scr_printf(" (Recoverable with soldering.)\n"); + + menuOptions.clear(); + menuOptions.emplace_back(MenuOption{ "NO", true }); + menuOptions.emplace_back(MenuOption{ "YES", true }); + + if (ShowMenu(10, 11, menuOptions) != 1) + return; + + Stage_InstallHackChosen(whichHack); +} + + +bool Stage_DumpMechaconROM() +{ + SimpleDebugOutput debug; + + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("Dump Mechacon ROM"); + scr_setXY(10, 7); + scr_printf("Are you sure you want to dump your Mechacon ROM to USB? This will"); + scr_setXY(10, 8); + scr_printf("overwrite any previous Mechacon ROM dump on your USB stick."); + + std::vector menuOptions; + menuOptions.emplace_back(MenuOption{ "NO", true }); + menuOptions.emplace_back(MenuOption{ "YES", true }); + + if (ShowMenu(10, 11, menuOptions) != 1) + return false; + + if (!IsBackDoorFunctioning()) + { + if (!CheckAndWaitForBackdoor()) + return false; + } + + scr_setbgcolor(0xFF800000); + scr_clear(); + scr_setXY(0, 5); + scr_printf(" Dumping Mechacon... (this can take 15 seconds to start)\n"); + scr_printf("\n"); + scr_printf(" | |\n"); + scr_printf("\n"); + scr_printf(" Hold triangle to cancel.\n"); + + // Dump keystore. This will take 15 seconds if we don't know where the payload gets + // uploaded to. However, once done, we'll remember, so the firmware dump won't wait. + std::vector keystore = DumpMechaconKeystoreWithPayload(payload_keystoredump, + size_payload_keystoredump, debug); + bool cancelled = false; + { + int buttons = 0; + if (GetPressedButtons(buttons) && (buttons & PAD_TRIANGLE)) + { + cancelled = true; + return false; + } + } + + // Dump main firmware. + std::vector rom = DumpMechaconROMFastWithPayload(payload_fastdump, size_payload_fastdump, + [&](size_t current, size_t total) -> bool + { + int buttons = 0; + if (GetPressedButtons(buttons) && (buttons & PAD_TRIANGLE)) + { + cancelled = true; + return false; + } + + float percentage = 100.0f * static_cast(current) / static_cast(total); + int chars = static_cast(percentage) / 2; + if (chars < 0) + chars = 0; + else if (chars > 50) + chars = 50; + // scr_print will print an extra space, so subtract 1. + if (chars >= 1) + { + char spaces[51]; + std::memset(spaces, ' ', 50); + spaces[chars - 1] = '\0'; + + scr_setXY(10 + 1, 7); + scr_setbgcolor(0xFF000080); + scr_printf("%s", spaces); + scr_setbgcolor(0xFF800000); + } + return true; + }, debug); + + if (cancelled) + return false; + + scr_setXY(0, 9); + scr_printf(" Saving... \n"); + + bool success = false; + std::string dumpPath; + if (!keystore.empty() && !rom.empty()) + { + bool successKeystore = false; + std::FILE* keystoreFile = std::fopen("mass:/mechacon-keystore.bin", "wb"); + if (keystoreFile) + { + if (std::fwrite(keystore.data(), 1, keystore.size(), keystoreFile) == keystore.size()) + successKeystore = true; + std::fclose(keystoreFile); + } + + bool successROM = false; + dumpPath = std::string("mass:/") + MakeDumpFilename(); + std::FILE* romFile = std::fopen(dumpPath.c_str(), "wb"); + if (romFile) + { + if (std::fwrite(rom.data(), 1, rom.size(), romFile) == rom.size()) + successROM = true; + std::fclose(romFile); + } + + success = successKeystore && successROM; + } + + if (success) + scr_printf(" Success! File dumped as %s\n", dumpPath.c_str()); + else + scr_printf(" FAILED! Could not write file to USB\n"); + + scr_printf(" Press X or O to continue.\n"); + WaitForXorOConfirm(); + return success; +} + + +[[noreturn]] void MainMenu() +{ + for (;;) + { + NvmState nvmState = GetNVMState(); + + bool allowNVMBackup = false; + bool allowNVMRestore = false; + bool allowInstallHack = false; + bool allowDumpMechacon = false; + + bool backdoorInstalled = false; + bool backdoorFunctioning = IsBackDoorFunctioning(); + + const char* nvmStateString; + switch (nvmState) + { + case NVM_STATE_CDPROTECT_HOOK: + nvmStateString = "Backdoored (CD Protect)"; + allowNVMBackup = false; + backdoorInstalled = true; + break; + case NVM_STATE_IRQ_HOOK: + nvmStateString = "Backdoored (IRQ Handler)"; + allowNVMBackup = false; + backdoorInstalled = true; + break; + case NVM_STATE_EMPTY: + nvmStateString = "Empty"; + allowNVMBackup = true; + backdoorInstalled = false; + break; + case NVM_STATE_OTHER: + default: + nvmStateString = "Default"; + allowNVMBackup = true; + backdoorInstalled = false; + break; + } + + allowInstallHack = true; + allowNVMRestore = backdoorFunctioning || backdoorInstalled; + allowDumpMechacon = backdoorFunctioning || backdoorInstalled; + + std::FILE* backup = std::fopen("mass:/mechadump_eeprom_backup.bin", "rb"); + if (backup) + std::fclose(backup); + else + allowNVMRestore = false; + + scr_setbgcolor(0xFF800000); + scr_clear(); + + scr_setXY(10, 5); + scr_printf("Patch state: %s", nvmStateString); + scr_setXY(39, 10); + scr_setbgcolor(0xFF800080); + scr_printf(" MAIN MENU"); + + std::vector menuOptions; + menuOptions.emplace_back(MenuOption{ "Back Up EEPROM", allowNVMBackup }); + menuOptions.emplace_back(MenuOption{ "Restore EEPROM", allowNVMRestore }); + menuOptions.emplace_back(MenuOption{ "Install Backdoor", allowInstallHack }); + menuOptions.emplace_back(MenuOption{ "Dump Mechacon ROM", allowDumpMechacon }); + menuOptions.emplace_back(MenuOption{ "Exit", true }); + + size_t choice = ShowMenu(10, 13, menuOptions); + + switch (choice) + { + case 0: + if (allowNVMBackup) + Stage_BackupNVM(); + break; + case 1: + if (allowNVMRestore) + Stage_RestoreNVM(); + break; + case 2: + if (allowInstallHack) + Stage_InstallHack(); + break; + case 3: + if (allowDumpMechacon) + Stage_DumpMechaconROM(); + break; + case 4: + ExecOSD(0, nullptr); + for (;;) + sleep(1); + default: + break; + } + } +} + + +int main(int argc, char** argv) +{ + assert(size_patch_irq_hook == 0xE0); + assert(size_patch_cdprotect_hook == 0xE0); + + init_scr(); + HackToHideCursor(); + scr_setXY(0, 5); + scr_printf(" Loading...\n"); + + ResetEverything(!argv[0]); + scr_printf(" Past ResetEverything\n"); + + mcInit(MC_TYPE_XMC); + scr_printf(" Past mcInit\n"); + mtapInit(); + scr_printf(" Past mtapInit\n"); + padInit(0); + scr_printf(" Past padInit\n"); + mtapPortOpen(0); + scr_printf(" Past mtapPortOpen\n"); + padPortOpen(0, 0, g_pad0Buffer); + scr_printf(" Past padPortOpen\n"); + sceCdInit(SCECdINoD); + scr_printf(" Past sceCdInit\n"); + + WarningScreen(); + + if (!SysinfoScreen()) + { + SleepThread(); + return 0; + } + + MainMenu(); +} diff --git a/mechadump/sysinfo.cpp b/mechadump/sysinfo.cpp new file mode 100644 index 0000000..5cfaa66 --- /dev/null +++ b/mechadump/sysinfo.cpp @@ -0,0 +1,136 @@ +#include "common.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "cipher.h" +#include "keys.hpp" +#include "util.h" + +#include "sysinfo.hpp" + + +bool GetMechaconVersion(int& major, int& minor, std::string& refreshDate) +{ + uint8_t params[1]; + + uint8_t resultVersion[3]; + params[0] = 0x00; + if (!sceCdApplySCmd(0x03, params, sizeof(params), resultVersion, sizeof(resultVersion))) + return false; + + uint8_t resultDate[6]; + params[0] = 0xFD; + if (!sceCdApplySCmd(0x03, params, sizeof(params), resultDate, sizeof(resultDate))) + return false; + if (resultDate[0] != 0x00) + return false; + + major = resultVersion[1]; + minor = resultVersion[2]; + + char dateString[64]; + std::snprintf(dateString, sizeof(dateString), "20%02x/%02x/%02x %02x:%02x", + resultDate[1], resultDate[2], resultDate[3], resultDate[4], resultDate[5]); + refreshDate = dateString; + + return true; +} + + +bool CheckNVMBlockChecksum(const uint8_t* block, size_t blockSize) +{ + assert(blockSize >= 2u); + + unsigned sum = 0; + for (size_t i = 0; i < blockSize - 2; ++i) + sum += block[i]; + + return static_cast(~sum) == block[blockSize - 1]; +} + + +bool DecodeRegionFlags(const uint8_t* eeprom, uint32_t& regionFlags) +{ + static constexpr uint8_t c_zeroIV[8] = {}; + + // Validate the EEPROM block checksums. + if (!CheckNVMBlockChecksum(&eeprom[0x1C6], 0x0A)) + return false; + if (!CheckNVMBlockChecksum(&eeprom[0x1D0], 0x0A)) + return false; + + // Derive the key that encrypts the region code. + uint8_t regionKey[8]; + uint8_t globalFlagsKey[8]; + write_be_uint64(globalFlagsKey, g_globalFlagsKey); + cipherCbcEncrypt(regionKey, &eeprom[0x1C6], 8, globalFlagsKey, 1, c_zeroIV); + + // Decrypt the region value. + uint8_t decrypted[8]; + cipherCbcDecrypt(decrypted, &eeprom[0x1D0], 8, regionKey, 1, c_zeroIV); + + // Validate internal checksum on the region. + uint32_t regionCode = read_le_uint32(&decrypted[0]); + uint16_t regionRandom = read_le_uint16(&decrypted[4]); + uint16_t regionChecksum = read_le_uint16(&decrypted[6]); + + unsigned regionCorrect = (0u + regionRandom + (regionCode & 0xFFFF) + (regionCode >> 16)) & 0xFFFF; + if (regionChecksum != regionCorrect) + return false; + + regionFlags = regionCode; + return true; +} + + +std::string GetModelString() +{ + char output[17]; + uint8_t params[1]; + uint8_t result[9]; + + params[0] = 0; + if (!sceCdApplySCmd(0x17, params, sizeof(params), result, sizeof(result))) + return std::string(); + if (result[0] != 0x00) + return std::string(); + + std::memcpy(&output[0], &result[1], sizeof(result) - 1); + + params[0] = 8; + if (!sceCdApplySCmd(0x17, params, sizeof(params), result, sizeof(result))) + return std::string(); + if (result[0] != 0x00) + return std::string(); + + std::memcpy(&output[8], &result[1], sizeof(result) - 1); + + output[16] = '\0'; + return output; +} + + +std::vector MakeEmptyPatchset() +{ + static constexpr uint8_t c_zeroIV[8] = {}; + + uint8_t encryptedZeros[8]; + + uint8_t mechaPatchKey[8]; + write_be_uint64(mechaPatchKey, g_mechaPatchKey); + cipherCbcEncrypt(encryptedZeros, c_zeroIV, 8, mechaPatchKey, 1, c_zeroIV); + + std::vector result; + result.reserve(0xE0); + result.insert(result.end(), encryptedZeros + 0, encryptedZeros + 8); + result.insert(result.end(), encryptedZeros + 0, encryptedZeros + 8); + for (int i = 0; i < 0xD0; ++i) + result.emplace_back(0xFF); + return result; +} diff --git a/mechadump/sysinfo.hpp b/mechadump/sysinfo.hpp new file mode 100644 index 0000000..7996ebd --- /dev/null +++ b/mechadump/sysinfo.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + + +bool GetMechaconVersion(int& major, int& minor, std::string& refreshDate); +bool DecodeRegionFlags(const uint8_t* eeprom, uint32_t& regionFlags); +std::string GetModelString(); +std::vector MakeEmptyPatchset(); diff --git a/mechapatchtool/Makefile b/mechapatchtool/Makefile new file mode 100644 index 0000000..a96c241 --- /dev/null +++ b/mechapatchtool/Makefile @@ -0,0 +1,35 @@ +BIN_DIR = bin$(OUT_SUFFIX) +ifndef OUT_DIR +OUT_DIR = $(BIN_DIR) +endif + +CXX_OBJECTS = mechapatchtool.o +TARGET_CXX_OBJECTS = $(foreach obj,$(CXX_OBJECTS),$(BIN_DIR)/$(obj)) + +EXE = $(OUT_DIR)/mechapatchtool + +CC = $(TOOL_PREFIX)gcc +CXX = $(TOOL_PREFIX)g++ +LD = $(TOOL_PREFIX)g++ + +BASEFLAGS = -O2 -Wall $(DEFINES) -I$(INCLUDE_DIR) +CCFLAGS = $(BASEFLAGS) -std=c11 +CXXFLAGS = $(BASEFLAGS) -std=c++14 + +.PHONY: all clean + +all: $(BIN_DIR) $(OUT_DIR) $(EXE) + +clean: + rm -f $(BIN_DIR)/*.o $(BIN_DIR)/*.a $(EXE) + +$(BIN_DIR): + mkdir $@ +$(OUT_DIR): + mkdir $@ + +$(EXE): $(TARGET_CXX_OBJECTS) | $(OUT_DIR) + $(LD) $^ -L$(LIB_DIR) -lmechacrypto -o $@ + +$(TARGET_CXX_OBJECTS): $(BIN_DIR)/%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $^ -o $@ diff --git a/mechapatchtool/mechapatchtool.cpp b/mechapatchtool/mechapatchtool.cpp new file mode 100644 index 0000000..8db96f7 --- /dev/null +++ b/mechapatchtool/mechapatchtool.cpp @@ -0,0 +1,911 @@ +#define _CRT_SECURE_NO_DEPRECATE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cipher.h" +#include "keys.hpp" +#include "util.h" + +using std::uint8_t; +using std::uint16_t; +using std::uint32_t; +using std::uint64_t; +using std::size_t; + + +const char* g_patchcryptInFilename = nullptr; + + +static_assert(sizeof(uint8_t) == 1, "unexpected uint8_t size"); + + +// Useful value +static constexpr uint8_t c_zeroIV[8] = {}; + + +[[noreturn]] void FatalErrorWithFilenameV(const char* filename, const char* format, va_list args) +{ + std::fprintf(stderr, "patchcrypt: %s: ", filename); + std::vfprintf(stderr, format, args); + std::fprintf(stderr, "\n"); + + std::fflush(stderr); + std::exit(1); +} + + +[[noreturn]] void FatalError(const char* format, ...) +{ + va_list args; + va_start(args, format); + FatalErrorWithFilenameV(g_patchcryptInFilename, format, args); + va_end(args); +} + + +std::vector LoadFile(const char* filename) +{ + do + { + std::FILE* file = std::fopen(filename, "rb"); + if (!file) + break; + + if (std::fseek(file, 0, SEEK_END)) + break; + + long lengthLong = std::ftell(file); + + if (std::fseek(file, 0, SEEK_SET) || (lengthLong < 0)) + break; + + size_t length = static_cast(lengthLong); + + std::vector data; + data.resize(length); + + if (std::fread(data.data(), 1, length, file) != length) + break; + + std::fclose(file); + return data; + + } while (false); + + FatalError("%s", strerror(errno)); + std::exit(1); +} + + +int SaveFile(const char* filename, const std::vector& data) +{ + do + { + std::FILE* file = std::fopen(filename, "wb"); + if (!file) + break; + + if (std::fwrite(data.data(), 1, data.size(), file) != data.size()) + break; + + std::fclose(file); + return 0; + + } while (false); + + FatalError("%s", strerror(errno)); + return 1; +} + + +// List of Thumb replacement instructions for doing fixups. +struct ThumbReplacements +{ + ThumbReplacements(); + + std::vector m_universal; + std::vector m_byRegister[16]; +}; + + +// Builds a list of Thumb opcodes that have no side effects other than destroying +// flags and the registers in registerMask. +ThumbReplacements::ThumbReplacements() +{ + // "movs low, low" is always allowed because we allow destroying flags. + for (unsigned reg = 0u; reg < 8u; ++reg) + { + unsigned opcode = 0b0001110'000'000000; + opcode |= reg << 3; + opcode |= reg; + m_universal.push_back(static_cast(opcode)); + } + + // "mov high, high" is always allowed for any register except PC. + // Notably, "nop" is actually "mov r8, r8". + for (unsigned reg = 8u; reg < 15u; ++reg) + { + unsigned opcode = 0b01000110'00000000; + opcode |= ((reg & 8) << 4) | (reg & 7); + opcode |= reg << 3; + m_universal.push_back(static_cast(opcode)); + } + + // Stuff for low registers. + for (unsigned reg = 0u; reg < 8u; ++reg) + { + std::vector& list = m_byRegister[reg]; + unsigned opcode00; + + // movs reg, #any8 + opcode00 = 0b00100'000'00000000; + opcode00 |= reg << 8; + for (unsigned b = 0; b < 0x100; ++b) + { + list.push_back(static_cast(opcode00 | b)); + } + + // adds reg, #any8 + opcode00 = 0b00110'000'00000000; + opcode00 |= reg << 8; + for (unsigned b = 0; b < 0x100; ++b) + { + list.push_back(static_cast(opcode00 | b)); + } + + // subs reg, #any8 + opcode00 = 0b00111'000'00000000; + opcode00 |= reg << 8; + for (unsigned b = 0; b < 0x100; ++b) + { + list.push_back(static_cast(opcode00 | b)); + } + + // lsls reg, anyreg, #any5 + opcode00 = 0b00000'00000'000'000; + opcode00 |= reg; + for (unsigned shift = 0; shift < 32; ++shift) + { + for (unsigned otherreg = 0; otherreg < 8; ++otherreg) + { + list.push_back(static_cast(opcode00 | (shift << 6) | (otherreg << 3))); + } + } + + // lsrs reg, anyreg, #any5 + opcode00 = 0b00001'00000'000'000; + opcode00 |= reg; + for (unsigned shift = 0; shift < 32; ++shift) // shift 0 means 32 for LSRS + { + for (unsigned otherreg = 0; otherreg < 8; ++otherreg) + { + list.push_back(static_cast(opcode00 | (shift << 6) | (otherreg << 3))); + } + } + + // asrs reg, anyreg, #any5 + opcode00 = 0b00010'00000'000'000; + opcode00 |= reg; + for (unsigned shift = 0; shift < 32; ++shift) // shift 0 means 32 for ASRS + { + for (unsigned otherreg = 0; otherreg < 8; ++otherreg) + { + list.push_back(static_cast(opcode00 | (shift << 6) | (otherreg << 3))); + } + } + } + + // That was probably overkill. We'll find a match >99% of the time from the above... for one register. +} + + +// Checks whether a "row" (16 bytes) is WriteConfig compatible. +bool IsWriteConfigCompatible(const uint8_t* row) +{ + unsigned sum = 0; + for (int i = 0; i < 15; ++i) + { + sum += row[i]; + } + + return row[15] == static_cast(sum); +} + + +void VerifyPatchData(const std::vector& input, bool& isNonzero, bool& writeConfigCompatible, bool& blockChecksumOK, bool& addressChecksumOK) +{ + // Check WriteConfig compatibility. + writeConfigCompatible = true; + for (int row = 0; row < 0xE; ++row) + { + if (!IsWriteConfigCompatible(&input[row * 0x10])) + { + writeConfigCompatible = false; + break; + } + } + + // Initialize the key. The DES code we're using always does CBC mode, so turn it into ECB by doing + // a single block decrypt with a zero IV. + uint8_t key[8]; + write_be_uint64(key, g_mechaPatchKey); + + // Check for a null patch list. + uint8_t patchList[16]; + std::memcpy(patchList, input.data(), sizeof(patchList)); + + cipherCbcDecrypt(&patchList[0], &patchList[0], 8, key, 1, c_zeroIV); + cipherCbcDecrypt(&patchList[8], &patchList[8], 8, key, 1, c_zeroIV); + + isNonzero = false; + for (int i = 0; i < 16; ++i) + { + if (patchList[i] != 0) + { + isNonzero = true; + break; + } + } + + // Check the two blocks' checksums. + blockChecksumOK = true; + for (int block = 0; block < 2; ++block) + { + int offset = block * 0x70; + unsigned sum = 0; + for (int i = 0; i < 0x6E; ++i) // byte 0x6E not counted + { + sum += input[offset + i]; + } + + if (input[offset + 0x6F] != static_cast(~sum)) + { + blockChecksumOK = false; + } + } + + // Check the patch address checksum. It's a wrapping checksum. + uint32_t addressSum = 0; + for (int i = 0; i < 4; ++i) + { + addressSum = static_cast(addressSum + read_le_uint32(&patchList[i * sizeof(uint32_t)])); + } + addressSum = static_cast(~addressSum); + uint32_t allegedAddressSum = read_le_uint32(&input[0xDA]); + addressChecksumOK = allegedAddressSum == addressSum; +} + + +std::vector DecryptUnpackPatchData(const std::vector& encrypted) +{ + assert(encrypted.size() == 0xE0); + + std::vector decrypted; + decrypted.resize(0xD8); + + // Combine the two blocks. + std::memcpy(decrypted.data(), encrypted.data(), 0x6E); + std::memcpy(decrypted.data() + 0x6E, encrypted.data() + 0x70, 0x6A); + + // Initialize the key. The DES code we're using always does CBC mode, so turn it into ECB by doing + // a single block decrypt with a zero IV. + uint8_t key[8]; + write_be_uint64(key, g_mechaPatchKey); + + // Decrypt the blocks. + for (int i = 0; i < 0xD8; i += 8) + { + cipherCbcDecrypt(decrypted.data() + i, decrypted.data() + i, 8, key, 1, c_zeroIV); + } + + return decrypted; +} + + +std::vector EncryptPackPatchData(const std::vector& decrypted, bool writeConfigFixup6andD) +{ + std::vector encrypted; + encrypted.resize(0xE0); + + // Compute the header checksum. + uint32_t addressSum = 0; + for (int i = 0; i < 16; i += 4) + { + addressSum = static_cast(addressSum + read_le_uint32(decrypted.data() + i)); + } + addressSum = static_cast(~addressSum); + + // Copy the decrypted data, padding out to the maximum size. + assert(decrypted.size() <= 0xD8); + std::memcpy(encrypted.data(), decrypted.data(), decrypted.size()); + std::memset(encrypted.data() + decrypted.size(), 0, 0xE0 - decrypted.size()); + + // Initialize the key. The DES code we're using always does CBC mode, so turn it into ECB by doing + // a single block decrypt with a zero IV. + uint8_t key[8]; + write_be_uint64(key, g_mechaPatchKey); + + // Encrypt the blocks. We'll deal with the block split after this.. + for (int i = 0; i < 0xD8; i += 8) + { + cipherCbcEncrypt(encrypted.data() + i, encrypted.data() + i, 8, key, 1, c_zeroIV); + } + + // Split the blocks. + std::memmove(encrypted.data() + 0x70, encrypted.data() + 0x6E, 0x6E); + + // Header checksum. + write_le_uint32(encrypted.data() + 0xDA, addressSum); + + // Block checksums. + for (int block = 0; block < 2; ++block) + { + int offset = block * 0x70; + unsigned sum = 0; + for (int i = 0; i < 0x6E; ++i) // byte 0x6E not counted + { + sum += encrypted[offset + i]; + } + + encrypted[offset + 0x6F] = static_cast(~sum); + + // Unused bytes. If requested, set to the value that makes a WriteConfig checksum pass + // for the final row. + if (writeConfigFixup6andD) + { + unsigned row6Sum = 0x00; + for (int i = 0x60; i < 0x6E; ++i) + { + row6Sum += encrypted[offset + i]; + } + encrypted[offset + 0x6E] = static_cast(encrypted[offset + 0x6F] - row6Sum); + } + else + { + encrypted[offset + 0x6E] = 0x00; + } + } + + return encrypted; +} + + +// Helper routine to extract patch data from an EEPROM dump. +// If it's already extracted, this does nothing. +bool ExtractPatchDataFromEEPROM(std::vector& dump) +{ + if (dump.size() == 0xE0) + { + // Already extracted. + return true; + } + else if (dump.size() == 0x400) + { + // Presumably an EEPROM dump. + dump.erase(dump.begin(), dump.begin() + 0x320); + return true; + } + else + { + return false; + } +} + + +// Verifies that a patch file's checksums are valid. +int VerifyPatchFile(const char* inFilename) +{ + g_patchcryptInFilename = inFilename; + std::vector input = LoadFile(inFilename); + + if (!ExtractPatchDataFromEEPROM(input)) + { + FatalError("file is not a patch file"); + } + + assert(input.size() == 0xE0); + + bool isNonzero; + bool writeConfigCompatible; + bool blockChecksumOK; + bool addressChecksumOK; + VerifyPatchData(input, isNonzero, writeConfigCompatible, blockChecksumOK, addressChecksumOK); + + std::printf("Patch has data: %s\n", isNonzero ? "YES" : "NO"); + std::printf("WriteConfig exploit compatible: %s\n", writeConfigCompatible ? "YES" : "NO"); + std::printf("Block checksums OK: %s\n", blockChecksumOK ? "YES" : "NO"); + std::printf("Address checksum OK: %s\n", addressChecksumOK ? "YES" : "NO"); + + // isNonzero isn't relevant for success + return (writeConfigCompatible && blockChecksumOK && addressChecksumOK) ? 0 : 1; +} + + +// Decrypts a patch file. +int DecryptPatchFile(const char* inFilename, const char* outFilename) +{ + g_patchcryptInFilename = inFilename; + std::vector input = LoadFile(inFilename); + + if (!ExtractPatchDataFromEEPROM(input)) + { + FatalError("file is not a patch file"); + } + + assert(input.size() == 0xE0); + + std::vector output = DecryptUnpackPatchData(input); + + return SaveFile(outFilename, output); +} + + +// Encrypts a patch file. +int EncryptPatchFile(const char* inFilename, const char* outFilename) +{ + g_patchcryptInFilename = inFilename; + std::vector input = LoadFile(inFilename); + + assert(input.size() == 0xD8); + + std::vector output = EncryptPackPatchData(input, false); + + return SaveFile(outFilename, output); +} + + + +struct Fixup +{ + uint32_t m_type; + uint32_t m_offset; + + bool operator<(const Fixup& other) const + { + return m_offset < other.m_offset; + } +}; + +enum FixupType : uint32_t +{ + FixupType_AnyUint8 = 0, + FixupType_AnyUint16 = 1, + FixupType_PatchAddress = 2, + FixupType_ThumbRegDestroy = 3, + FixupType_NumFixupTypes +}; +static const unsigned char s_fixupSizeByType[] = +{ + sizeof(uint8_t), // FixupType_AnyUint8 + sizeof(uint16_t), // FixupType_AnyUint16 + sizeof(uint32_t), // FixupType_PatchAddress + sizeof(uint16_t), // FixupType_ThumbRegDestroy +}; +static_assert(sizeof(s_fixupSizeByType) == FixupType_NumFixupTypes, "s_fixupSizeByType is wrong size"); + +uint32_t FixupAnyUint8_CountPossible(const uint8_t*) +{ + return uint32_t(uint8_t(-1)) + 1; +} +void FixupAnyUint8_Apply(uint32_t counter, uint8_t* overwrite, const uint8_t*) +{ + assert(counter <= uint8_t(-1)); + *overwrite = static_cast(counter); +} + +uint32_t FixupAnyUint16_CountPossible(const uint8_t*) +{ + return uint32_t(uint16_t(-1)) + 1; +} +void FixupAnyUint16_Apply(uint32_t counter, uint8_t* overwrite, const uint8_t*) +{ + assert(counter <= uint16_t(-1)); + write_le_uint16(overwrite, static_cast(counter)); +} + +enum : uint32_t +{ + // Address range we don't care about messing with in a patch. + PATCHADDRESS_FIXUP_START = 0x44000, // past the end + PATCHADDRESS_FIXUP_SIZE = 0x1000, +}; +uint32_t FixupPatchAddress_CountPossible(const uint8_t*) +{ + return PATCHADDRESS_FIXUP_SIZE / 4; +} +void FixupPatchAddress_Apply(uint32_t counter, uint8_t* overwrite, const uint8_t*) +{ + assert(counter < (PATCHADDRESS_FIXUP_SIZE / 4)); + write_le_uint32(overwrite, PATCHADDRESS_FIXUP_START + (counter * 4)); +} + +static ThumbReplacements s_thumbReplacements; +uint32_t FixupThumbRegDestroy_CountPossible(const uint8_t* original) +{ + unsigned regList = read_le_uint16(original); + size_t count = s_thumbReplacements.m_universal.size(); + for (int i = 0; i < 16; ++i) + { + if (regList & (1u << i)) + { + count += s_thumbReplacements.m_byRegister[i].size(); + } + } + return static_cast(count); +} +void FixupThumbRegDestroy_Apply(uint32_t counter, uint8_t* overwrite, const uint8_t* original) +{ + unsigned regList = read_le_uint16(original); + for (int i = -1; i < 16; ++i) + { + const std::vector* current; + if (i < 0) + { + current = &s_thumbReplacements.m_universal; + } + else if (regList & (1u << i)) + { + current = &s_thumbReplacements.m_byRegister[i]; + } + else + { + continue; + } + + if (counter < current->size()) + { + write_le_uint16(overwrite, (*current)[counter]); + return; + } + else + { + counter -= static_cast(current->size()); + } + } + // counter too large + assert(false); +} + + +struct FixupHandler +{ + uint32_t(*m_countPossible)(const uint8_t* original); + void(*m_apply)(uint32_t counter, uint8_t* overwrite, const uint8_t* original); +}; +static const FixupHandler s_fixupHandlers[] = +{ + { FixupAnyUint8_CountPossible, FixupAnyUint8_Apply }, + { FixupAnyUint16_CountPossible, FixupAnyUint16_Apply }, + { FixupPatchAddress_CountPossible, FixupPatchAddress_Apply }, + { FixupThumbRegDestroy_CountPossible, FixupThumbRegDestroy_Apply }, +}; +static_assert(sizeof(s_fixupHandlers) / sizeof(s_fixupHandlers[0]) == FixupType_NumFixupTypes, "s_fixupHandlers is wrong size"); + + +int FixupPatchFile(const char* inFilename, const char* outFilename) +{ + g_patchcryptInFilename = inFilename; + std::vector input = LoadFile(inFilename); + + if (input.size() < sizeof(uint32_t) * 2) + FatalError("file too small"); + + uint32_t payloadLength = read_le_uint32(input.data() + input.size() - sizeof(uint32_t)); + if (payloadLength > input.size() - sizeof(uint32_t)) + FatalError("invalid payload length"); + if (payloadLength < 0x10) + FatalError("payload too short; must be at least 16 bytes"); + if (payloadLength > 0xD8) + FatalError("payload too long; must be 0xD8 bytes or shorter"); + + // Determine number of fixups. + size_t fixupByteLength = input.size() - payloadLength - sizeof(uint32_t); + if (fixupByteLength % (sizeof(uint32_t) * 2) != 0) + FatalError("non-integer number of fixups"); + + size_t fixupLength = fixupByteLength / (sizeof(uint32_t) * 2); + + std::vector fixups; + fixups.reserve(fixupLength); + + // Parse and validate fixup table. + for (size_t i = 0; i < fixupLength; ++i) + { + const uint8_t* p = input.data() + payloadLength + (i * sizeof(uint32_t) * 2); + uint32_t type = read_le_uint32(p); + uint32_t offset = read_le_uint32(p + sizeof(uint32_t)); + uint32_t size = s_fixupSizeByType[type]; + + if (type >= FixupType_NumFixupTypes) + FatalError("bad fixup type %" PRIu32, type); + if (offset >= payloadLength) + FatalError("fixup out of range at offset %02" PRIX32, offset); + if (payloadLength - offset < size) + FatalError("fixup at offset 0x%02" PRIX32 " goes past the end of payload", offset); + if ((offset >= 0x60) && (offset < 0x6E)) + FatalError("fixup at offset 0x%02" PRIX32 " is on row 6", offset); + if ((offset >= 0x6E) && ((offset & 0xF) >= 0xE)) + FatalError("fixup at offset 0x%02" PRIX32 " not allowed at +0xE row offsets after 0x60 for crypto reasons", offset); + if (i > 0) + { + if (offset < fixups[i - 1].m_offset) + FatalError("fixup at offset 0x%02" PRIX32 " is out of order", offset); + if (offset < fixups[i - 1].m_offset + s_fixupSizeByType[fixups[i - 1].m_type]) + FatalError("fixup at offset 0x%02" PRIX32 " overlaps previous fixup", offset); + } + + fixups.emplace_back(Fixup{ type, offset }); + } + + // For any unused bytes, put a single-byte fixup. + for (unsigned i = payloadLength; i < 0xD8; ++i) + { + fixups.emplace_back(Fixup{ FixupType_AnyUint8, i }); + } + + // Buffer for sketching out alternatives. + std::vector sketch = input; + sketch.resize(payloadLength); + sketch.resize(0xD8); + + // Chain of counters for up to 16 fixups. + std::vector rowCounters; + rowCounters.reserve(16); + std::vector rowPossible; + rowPossible.reserve(16); + + // Initialize the key. The DES code we're using always does CBC mode, so turn it into ECB by doing + // a single block decrypt with a zero IV. + uint8_t key[8]; + write_be_uint64(key, g_mechaPatchKey); + + // Handle the rows. + for (unsigned row = 0x0; row <= 0xD; ++row) + { + // Rows 6 and D are handled by EncryptPackPatchData. + if ((row == 0x6) || (row == 0xD)) + continue; + + // For row 7 and later, two of our bytes are part of the previous encryption block, but affect + // our checksum too. So we need to encrypt the previous block to know what our target is. + uint8_t previousBlock[8]{}; + unsigned rowFixupSize = 0x10; + if (row >= 0x7) + { + cipherCbcEncrypt(previousBlock, sketch.data() + (row * 0x10) - 8, 8, key, 1, c_zeroIV); + rowFixupSize = 0xE; + } + + // Find fixups relevant to this row. + auto fixupBegin = std::lower_bound(fixups.begin(), fixups.end(), Fixup{ 0, row * 0x10 }); + auto fixupEnd = std::upper_bound(fixups.begin(), fixups.end(), Fixup{ 0, row * 0x10 + rowFixupSize - 1 }); + + // Fixup loop. The loop is designed so that a null fixup set works if it already matches. + rowCounters.resize(static_cast(fixupEnd - fixupBegin)); + + // Determine number of options for each fixup. + rowPossible.resize(rowCounters.size()); + for (size_t row = 0; row < rowCounters.size(); ++row) + { + rowCounters[row] = 0; + rowPossible[row] = (*s_fixupHandlers[fixupBegin[row].m_type].m_countPossible)(input.data() + fixupBegin[row].m_offset); + if (rowPossible[row] == 0) + FatalError("fixup at offset %02" PRIX32 " has no possible values", fixupBegin[row].m_offset); + } + + for (;;) + { + // Set the data bytes according to their counters. + for (size_t row = 0; row < rowCounters.size(); ++row) + { + (*s_fixupHandlers[fixupBegin[row].m_type].m_apply)(rowCounters[row], sketch.data() + fixupBegin[row].m_offset, + input.data() + fixupBegin[row].m_offset); + } + + // Encrypt the block. + uint8_t encrypted[16]; + cipherCbcEncrypt(&encrypted[0], sketch.data() + (row * 0x10), 8, key, 1, c_zeroIV); + cipherCbcEncrypt(&encrypted[8], sketch.data() + (row * 0x10) + 8, 8, key, 1, c_zeroIV); + + if (row >= 0x7) + { + // The block to check for a match is misaligned. + uint8_t checkBlock[16]; + std::memcpy(&checkBlock[0], &previousBlock[6], 2); + std::memcpy(&checkBlock[2], &encrypted[0], 14); + if (IsWriteConfigCompatible(checkBlock)) + break; + } + else + { + if (IsWriteConfigCompatible(encrypted)) + break; + } + + // Increment counters. + bool overflow = true; + for (size_t row = 0; row < rowCounters.size(); ++row) + { + ++(rowCounters[row]); + if (rowCounters[row] == rowPossible[row]) + { + // Carry + rowCounters[row] = 0; + continue; + } + overflow = false; + break; + } + if (overflow) + FatalError("ran out of possibilities for match on row %X", row); + } + } + + // Encrypt the data, which also handles rows 6 and D. + std::vector encrypted = EncryptPackPatchData(sketch, true); + + bool isNonzero; + bool writeConfigCompatible; + bool blockChecksumOK; + bool addressChecksumOK; + VerifyPatchData(encrypted, isNonzero, writeConfigCompatible, blockChecksumOK, addressChecksumOK); + + std::printf("--- Fixup verification ---\n"); + std::printf("Patch has data: %s\n", isNonzero ? "YES" : "NO"); + std::printf("WriteConfig exploit compatible: %s\n", writeConfigCompatible ? "YES" : "NO"); + std::printf("Block checksums OK: %s\n", blockChecksumOK ? "YES" : "NO"); + std::printf("Address checksum OK: %s\n", addressChecksumOK ? "YES" : "NO"); + + if (!isNonzero || !writeConfigCompatible || !blockChecksumOK || !addressChecksumOK) + FatalError("fixup process failed to verify"); + + return SaveFile(outFilename, encrypted); +} + + +[[noreturn]] void SyntaxError() +{ + std::printf( + "Syntax:\n" + " Apply fixups to a compiled patch file:\n" + " mechapatchtool --fixup --in=infile --out=outfile\n" + " Verify the checksums on an encrypted patch file:\n" + " mechapatchtool --verify --in=infile\n" + " Decrypt an encrypted patch dump, either alone or in an EEPROM dump:\n" + " mechapatchtool --decrypt --in=infile --out=outfile\n" + " Encrypt a patch dump:\n" + " mechapatchtool --encrypt --in=infile --out=outfile\n" + "\n" + "Filenames can also be separate parameters (and don't use equal signs).\n"); + std::exit(1); +} + + +int main(int, char** argv) +{ + // Parameter declarations. + bool cmdFixup = false; + bool cmdVerify = false; + bool cmdDecrypt = false; + bool cmdEncrypt = false; + const char* inFilename = nullptr; + const char* outFilename = nullptr; + + struct ParamInfo + { + const char* m_name; + bool m_isCommand; + size_t m_strlen; + bool* m_bool; + const char** m_string; + }; + ParamInfo paramInfo[] = + { + { "--fixup", true, 0, &cmdFixup, nullptr }, + { "--verify", true, 0, &cmdVerify, nullptr }, + { "--decrypt", true, 0, &cmdDecrypt, nullptr }, + { "--encrypt", true, 0, &cmdEncrypt, nullptr }, + { "--in", false, 0, nullptr, &inFilename }, + { "--out", false, 0, nullptr, &outFilename }, + }; + for (ParamInfo& param : paramInfo) + { + param.m_strlen = std::strlen(param.m_name); + } + + // Evaluate parameters. + if (!argv[0]) + SyntaxError(); + + bool haveCommand = false; + + for (++argv; *argv; ++argv) + { + const char* arg = *argv; + size_t length = std::strlen(arg); + + for (const ParamInfo& param : paramInfo) + { + if (param.m_bool) + { + if ((length == param.m_strlen) && (std::memcmp(arg, param.m_name, length) == 0)) + { + if (haveCommand && param.m_isCommand) + SyntaxError(); + *param.m_bool = true; + if (param.m_isCommand) + haveCommand = true; + goto found; + } + } + + if (param.m_string) + { + size_t tempLength = length; + const char* equals = static_cast(std::memchr(arg, '=', tempLength)); + if (equals) + tempLength = static_cast(equals - arg); + + if ((tempLength == param.m_strlen) && (std::memcmp(arg, param.m_name, tempLength) == 0)) + { + if (*param.m_string) + SyntaxError(); + *param.m_string = equals ? equals + 1 : *++argv; + if (!*param.m_string) + SyntaxError(); + goto found; + } + } + } + + SyntaxError(); + + found:; + } + + if (!haveCommand) + SyntaxError(); + + if (cmdFixup) + { + if (!inFilename || !outFilename) + SyntaxError(); + + return FixupPatchFile(inFilename, outFilename); + } + else if (cmdVerify) + { + if (!inFilename || outFilename) + SyntaxError(); + + return VerifyPatchFile(inFilename); + } + else if (cmdDecrypt) + { + if (!inFilename || !outFilename) + SyntaxError(); + + return DecryptPatchFile(inFilename, outFilename); + } + else if (cmdEncrypt) + { + if (!inFilename || !outFilename) + SyntaxError(); + + return EncryptPatchFile(inFilename, outFilename); + } + else + { + assert(false); + } +} diff --git a/patch/Makefile b/patch/Makefile new file mode 100644 index 0000000..ae5d471 --- /dev/null +++ b/patch/Makefile @@ -0,0 +1,43 @@ +ifndef BIN_DIR +BIN_DIR = . +endif +ifndef BIN_DIR +OUT_DIR = . +endif + +FLAGS = -march=armv4t -mfloat-abi=soft -nostdlib + +AS = $(TOOL_PREFIX)gcc +LD = $(TOOL_PREFIX)gcc +OBJCOPY = $(TOOL_PREFIX)objcopy + +ASFLAGS = $(FLAGS) +LDFLAGS = $(FLAGS) +OBJCOPYFLAGS = -O binary + + +all: $(BIN_DIR) $(OUT_DIR) $(OUT_DIR)/patch-irq-hook.unfixed.bin $(OUT_DIR)/patch-cdprotect-hook.unfixed.bin + +$(BIN_DIR): + mkdir $(BIN_DIR) +$(OUT_DIR): + mkdir $(OUT_DIR) + +clean: + rm -f $(OUT_DIR)/*.bin $(BIN_DIR)/*.elf $(BIN_DIR)/*.o + +# patch-irq-hook +$(OUT_DIR)/patch-irq-hook.unfixed.bin: $(BIN_DIR)/patch-irq-hook.elf + $(OBJCOPY) $(OBJCOPYFLAGS) $^ $@ +$(BIN_DIR)/patch-irq-hook.elf: $(BIN_DIR)/patch-irq-hook.o patch.ld + $(LD) $(LDFLAGS) $(BIN_DIR)/patch-irq-hook.o -T patch.ld -o $(BIN_DIR)/patch-irq-hook.elf +$(BIN_DIR)/patch-irq-hook.o: patch-irq-hook.S + $(AS) -c $(ASFLAGS) $^ -o $@ + +# patch-cdprotect-hook +$(OUT_DIR)/patch-cdprotect-hook.unfixed.bin: $(BIN_DIR)/patch-cdprotect-hook.elf + $(OBJCOPY) $(OBJCOPYFLAGS) $^ $@ +$(BIN_DIR)/patch-cdprotect-hook.elf: $(BIN_DIR)/patch-cdprotect-hook.o patch.ld + $(LD) $(LDFLAGS) $(BIN_DIR)/patch-cdprotect-hook.o -T patch.ld -o $(BIN_DIR)/patch-cdprotect-hook.elf +$(BIN_DIR)/patch-cdprotect-hook.o: patch-cdprotect-hook.S + $(AS) -c $(ASFLAGS) $^ -o $@ diff --git a/patch/patch-cdprotect-hook.S b/patch/patch-cdprotect-hook.S new file mode 100644 index 0000000..cfb2c1a --- /dev/null +++ b/patch/patch-cdprotect-hook.S @@ -0,0 +1,243 @@ +.section ".text" +.syntax unified +.align 4 + +// Offset 0x00 +.thumb +.globl _start +_start: + // Addresses to patch... + .word 0x00000200 + .word 0x00000204 + .word 0x00000208 +fixup_row_0: + .word 0 + +// Offset 0x10 +.thumb +replacement: + movs r2, #0x28 + ldr r2, [r2] + adds r2, r2, #0x78 + ldr r2, [r2] + adds r2, r2, #1 + bx r2 +fixup_row_1: + .word 0xFFFFFFFF + + +// Offset 0x20 +// This offset is normally the SVC dispatch addresses, but because the initial +// patch set doesn't patch in an SVC, we can put an extra 16 bytes of code. +// +// This function is called from the patch code we placed at 0x00000200. +.thumb +initialize: + push {r0,r1} + +fixup_row_2: + // Safe to destroy r0,r1,r2,r3,lr,flags + .hword (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 14) + + // Search the ROM for the SCMD 03 dispatch table. + ldr r1, constant_000001A3 + ldr r2, constant_000009A4 + movs r0, #0 + // DEBUGGING ONLY - reduces search size + //ldr r0, constant_00038000 +search_loop: + ldmia r0!, {r3} + cmp r3, r1 + bne search_loop + //---- + ldr r3, [r0, #4] + cmp r3, r2 + bne search_loop + +fixup_row_3: + // Safe to destroy r1,r2,r3,lr,flags + .hword (1 << 1) | (1 << 2) | (1 << 3) | (1 << 14) + + // Unlock patch registers. + ldr r2, constant_CF7EE669 + ldr r1, constant_03880050 + strh r2, [r1] + lsrs r2, r2, #16 + //---- + strh r2, [r1] + + // Disable patches in patch registers. + subs r1, r1, #0x10 + movs r2, #0 +disable_patch_loop: + subs r1, r1, #0x10 + strb r2, [r1] + lsls r3, r1, #24 + bne disable_patch_loop + +fixup_row_4: + // Safe to destroy r2,r3,lr + .hword (1 << 2) | (1 << 3) | (1 << 14) + //---- + + // Hook the SCMD 03:A4 handler. + adr r2, scmd_03_A4_handler + // While we have scmd_03_A4_handler's address, write the address of the + // original handler for scmd_03_A4_handler to use. + ldr r3, [r0] // must this be done before patch register write? + str r3, [r2, #original_scmd_03_A4_handler - scmd_03_A4_handler] + // Now we finish the writes to the patch registers. + str r0, [r1, #0x04] + adds r2, r2, #1 + str r2, [r1, #0x08] + +fixup_row_5: + // Safe to destroy r0,r2,r3,lr + .hword (1 << 0) | (1 << 2) | (1 << 3) | (1 << 14) + + // Enable this patch. + movs r0, #1 + //---- + strb r0, [r1] + + // Lock patch registers. + adds r1, r1, #0x50 + ldr r2, constant_F6D778C7 + strh r2, [r1] + lsrs r2, r2, #16 + strh r2, [r1] + + // Resume with code we patched out. (r0 is 1, so << 9 = 0x200.) + lsls r2, r0, #9 + //---- *** ROW 6 IS ONLY 7 INSTRUCTIONS AND NO BRUTE FORCE HELP IS REQUIRED *** + // Restore critical registers. + pop {r0,r1} + // Use mov pc instead of bx for convenience, since this is thumb-to-thumb. + mov pc, r2 + + + // We're misaligned. Put any uint16 here. +fixup_row_7: + .hword 0xFFFF + + +// *** We just aligned. Cause assembler error if not so we can correct for this. +//.align 2 +.thumb +scmd_03_A4_handler: + nop // FIXME: This was an unneeded push instruction before + + // Read address of 03:A4 plus 4 written there by initialize. + ldr r0, original_scmd_03_A4_handler + subs r0, r0, #1 + // r0 contains the address of the original SCMD 03:A4 handler. + // Get the address of the SCMD parameter buffer. +4 is where the actual + // parameters are located. While we're at it, grab the constant + // 0x036000D0 (SCMD result FIFO, but minus 9). + ldr r1, [r0, #0x78] // 0x036000D0 + ldr r0, [r0, #0x70] // SCMD input buffer (+4 = parameters) + //---- + + // 03:A4 takes exactly one parameter, A4, or will fail. However, nothing + // clears the parameter buffer, so rely on that to get extra parameters. + // Check for magic bytes "MCD" after A4. + ldr r2, [r0, #4] + +fixup_row_8: + // Safe to destroy r3 + .hword (1 << 3) + + ldr r3, constant_44434DA4 + cmp r2, r3 + bne error + + ldr r2, [r0, #8] + cmp r2, #1 + ldr r2, [r0, #12] + //---- + ldr r3, [r0, #16] + beq execute // 1 = execute + bcs write // 2 = write + ldr r2, [r2] // 0 = load + b done + +fixup_row_9: + // Between code blocks; anything can be here. + .hword 0xFFFF + +execute: + bx r2 + +write: + str r3, [r2] + //---- + +done: + // r0 contains a RAM address (0x0200????), so we can shift it right to use + // to count to 4 instead of initializing it. + movs r3, #0x42 // success code +error: // R3's low byte will be 0xA4 + strb r3, [r1, #0x09] +result_loop: + strb r2, [r1, #0x09] // 0x036000D9 (SCMD result FIFO) + lsrs r2, r2, #8 + lsrs r0, r0, #8 + bne result_loop + + // An ordinary return works just fine. + nop // FIXME: This was an unneeded push instruction before + +fixup_row_A: + // Safe to destroy r0,r1,r2,r3 + .hword (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) + //---- + + bx lr + +// We should be aligned here; cause an error if not so we can correct it. +// However, in terms of the checksum system, we're misaligned. +//.align 2 +constant_000001A3: + .word 0x000001A3 +constant_000009A4: + .word 0x000009A4 +original_scmd_03_A4_handler: +fixup_row_B: + // Overwritten with address of 03:A4 handler. We can also use this field + // for the checksum brute-force. + .word 0xFFFFFFFF + //---- +constant_CF7EE669: + .word 0xCF7EE669 +constant_03880050: + .word 0x03880050 +constant_F6D778C7: + .word 0xF6D778C7 +fixup_row_C: + .word 0xFFFFFFFF +constant_44434DA4: + .word 0x44434DA4 + + +// WriteConfig checksum bypass information. +.align 2 +fixup_list_start: + // 1 = uint16 (or uint32) that can take any value possible + // 2 = special handling for an unneeded patch address + // 3 = thumb instruction allowed to modify flags and any specified register from uint16 + .word 2, fixup_row_0 - _start + .word 1, fixup_row_1 - _start + .word 3, fixup_row_2 - _start + .word 3, fixup_row_3 - _start + .word 3, fixup_row_4 - _start + .word 3, fixup_row_5 - _start + // Row 6 does not require brute force help, because it has the first 0x70-block checksum. + .word 1, fixup_row_7 - _start + .word 3, fixup_row_8 - _start + .word 1, fixup_row_9 - _start + .word 3, fixup_row_A - _start + .word 1, fixup_row_B - _start + .word 1, fixup_row_C - _start + + // The final uint32 is how big the primary payload area is. + .word fixup_list_start - _start diff --git a/patch/patch-irq-hook.S b/patch/patch-irq-hook.S new file mode 100644 index 0000000..7a12ee5 --- /dev/null +++ b/patch/patch-irq-hook.S @@ -0,0 +1,242 @@ +.section ".text" +.syntax unified +.align 4 + +// Offset 0x00 +.thumb +.globl _start +_start: + // Addresses to patch... + .word 0x00000164 + .word 0x00000168 + .word 0x0000016C +fixup_row_0: + .word 0 + +// Offset 0x10 +.arm +replacement: + mov r0, #0 + ldr r0, [r0, #0x28] + ldr pc, [r0, #0x78] +fixup_row_1: + .word 0xFFFFFFFF + + //---- ROW 2 +// Offset 0x20 +// This offset is normally the SVC dispatch addresses, but because the initial +// patch set doesn't patch in an SVC, we can put an extra 16 bytes of code. +// +// This function is called from the patch code we placed at 0x00000200. +.arm +initialize_thunk: + adr r0, initialize + 1 + bx r0 +.thumb +initialize: + push {r2-r3} + +fixup_row_2: + // Safe to destroy r0,r1,r2,r3,flags + .hword (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) + + // Search the ROM for the SCMD 03 dispatch table. + ldr r1, constant_000001A3 + ldr r2, constant_000009A4 + //---- ROW 3 + movs r0, #0 + // DEBUGGING ONLY - reduces search size + //ldr r0, constant_00038000 +search_loop: + ldmia r0!, {r3} + cmp r3, r1 + bne search_loop + ldr r3, [r0, #4] + cmp r3, r2 + bne search_loop + +fixup_row_3: + // Safe to destroy r1,r2,r3,flags + .hword (1 << 1) | (1 << 2) | (1 << 3) + //---- ROW 4 +fixup_row_4: + // Safe to destroy r1,r2,r3,flags + .hword (1 << 1) | (1 << 2) | (1 << 3) + + // Unlock patch registers. + ldr r2, constant_CF7EE669 + ldr r1, constant_03880050 + strh r2, [r1] + lsrs r2, r2, #16 + strh r2, [r1] + + // Disable patches in patch registers. + subs r1, r1, #0x10 + movs r2, #0 + //---- ROW 5 +disable_patch_loop: + subs r1, r1, #0x10 + strb r2, [r1] + lsls r3, r1, #24 + bne disable_patch_loop + +fixup_row_5: + // Safe to destroy r2,r3 + .hword (1 << 2) | (1 << 3) + + // Hook the SCMD 03:A4 handler. + adr r2, scmd_03_A4_handler + // While we have scmd_03_A4_handler's address, write the address of the + // original handler for scmd_03_A4_handler to use. + ldr r3, [r0] // must this be done before patch register write? + str r3, [r2, #original_scmd_03_A4_handler - scmd_03_A4_handler] + //---- ROW 6 (SHORT ROW - AND NO FIXUP REQUIRED) + // Now we finish the writes to the patch registers. + str r0, [r1, #0x04] + adds r2, r2, #1 + str r2, [r1, #0x08] + + // Enable this patch. + movs r0, #1 + strb r0, [r1] + + // Lock patch registers. + adds r1, r1, #0x50 + ldr r2, constant_F6D778C7 + //---- ROW 7 + strh r2, [r1] + lsrs r2, r2, #16 + strh r2, [r1] + + // Resume with code we patched out. + movs r0, #0x164 / 2 + adds r0, r0 +fixup_row_7: + .hword (1 << 1) | (1 << 2) | (1 << 3) + // Restore critical registers. + pop {r2-r3} + // Return. + bx r0 + + //---- ROW 8 + // This halfword is wasted due to alignment, so we might as well use it as + // a branch target for the below code. +execute: + bx r2 + + +// *** We just aligned. Cause assembler error if not so we can correct for this. +//.align 2 +.thumb +scmd_03_A4_handler: + // Read address of 03:A4 plus 4 written there by initialize. + ldr r0, original_scmd_03_A4_handler + subs r0, r0, #1 + // r0 contains the address of the original SCMD 03:A4 handler. + // Get the address of the SCMD parameter buffer. +4 is where the actual + // parameters are located. While we're at it, grab the constant + // 0x036000D0 (SCMD result FIFO, but minus 9). + ldr r1, [r0, #0x78] // 0x036000D0 + ldr r0, [r0, #0x70] // SCMD input buffer (+4 = parameters) + +fixup_row_8: + // Safe to destroy r2,r3 + .hword (1 << 2) | (1 << 3) + + // 03:A4 takes exactly one parameter, A4, or will fail. However, nothing + // clears the parameter buffer, so rely on that to get extra parameters. + // Check for magic bytes "MCD" after A4. + ldr r2, [r0, #4] + + ldr r3, constant_44434DA4 + //---- ROW 9 + cmp r2, r3 + bne error + +fixup_row_9: + // Safe to destroy r2,r3 + .hword (1 << 2) | (1 << 3) + + ldr r2, [r0, #8] + cmp r2, #1 + ldr r2, [r0, #12] + ldr r3, [r0, #16] + beq execute // 1 = execute + //---- ROW A + bcs write // 2 = write + ldr r2, [r2] // 0 = load + b done + +fixup_row_A: + // Between code blocks; anything can be here. + .hword 0xFFFF + +write: + str r3, [r2] + +done: + // r0 contains a RAM address (0x0200????), so we can shift it right to use + // to count to 4 instead of initializing it. + movs r3, #0x42 // success code +error: // R3's low byte will be 0xA4 + strb r3, [r1, #0x09] +result_loop: + strb r2, [r1, #0x09] // 0x036000D9 (SCMD result FIFO) + //---- ROW B + lsrs r2, r2, #8 + lsrs r0, r0, #8 + bne result_loop + +fixup_row_B: + // Safe to destroy r0,r1,r2,r3 + .hword (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) + + // An ordinary return works just fine. + bx lr + +// We should be aligned here; cause an error if not so we can correct it. +// However, in terms of the checksum system, we're misaligned. +//.align 2 +constant_000001A3: + .word 0x000001A3 +constant_000009A4: + .word 0x000009A4 + //---- ROW C +original_scmd_03_A4_handler: +fixup_row_C: + // Overwritten with address of 03:A4 handler. We can also use this field + // for the checksum brute-force. + .word 0xFFFFFFFF +constant_CF7EE669: + .word 0xCF7EE669 + //---- ROW D +constant_03880050: + .word 0x03880050 +constant_F6D778C7: + .word 0xF6D778C7 +constant_44434DA4: + .word 0x44434DA4 + + +// WriteConfig checksum bypass information. +.align 2 +fixup_list_start: + // 1 = uint16 (or uint32) that can take any value possible + // 2 = special handling for an unneeded patch address + // 3 = thumb instruction allowed to modify flags and any specified register from uint16 + .word 2, fixup_row_0 - _start + .word 1, fixup_row_1 - _start + .word 3, fixup_row_2 - _start + .word 3, fixup_row_3 - _start + .word 3, fixup_row_4 - _start + .word 3, fixup_row_5 - _start + // Row 6 does not require brute force help, because it has the first 0x70-block checksum. + .word 3, fixup_row_7 - _start + .word 3, fixup_row_8 - _start + .word 3, fixup_row_9 - _start + .word 1, fixup_row_A - _start + .word 3, fixup_row_B - _start + .word 1, fixup_row_C - _start + + // The final uint32 is how big the primary payload area is. + .word fixup_list_start - _start diff --git a/patch/patch.ld b/patch/patch.ld new file mode 100644 index 0000000..83696a9 --- /dev/null +++ b/patch/patch.ld @@ -0,0 +1,31 @@ +OUTPUT_FORMAT("elf32-littlearm") +ENTRY(_start) +phys = 0x00010000; +SECTIONS +{ + .text phys : AT(phys) { + code = .; + *(.text) + *(.rodata) + . = ALIGN(4); + } + .rodata : AT(phys + (rodata - code)) + { + rodata = .; + *(.rodata) + . = ALIGN(4); + } + .data : AT(phys + (data - code)) + { + data = .; + *(.data) + . = ALIGN(4); + } + .bss : AT(phys + (bss - code)) + { + bss = .; + *(.bss) + . = ALIGN(4); + } + end = .; +} diff --git a/payload/Makefile b/payload/Makefile new file mode 100644 index 0000000..5e5c1cd --- /dev/null +++ b/payload/Makefile @@ -0,0 +1,61 @@ +ifndef BIN_DIR +BIN_DIR = . +endif +ifndef BIN_DIR +OUT_DIR = . +endif + +CRT0 = $(BIN_DIR)/payload-crt0.o + +FLAGS = -march=armv4t -mfloat-abi=soft -nostdlib + +CC = $(TOOL_PREFIX)gcc +AS = $(TOOL_PREFIX)gcc +LD = $(TOOL_PREFIX)gcc +OBJCOPY = $(TOOL_PREFIX)objcopy + +# -fno-isolate-erroneous-paths-dereference is because 0 is a valid address +CCFLAGS = -O2 -std=c11 -Wall -fPIC -fno-isolate-erroneous-paths-dereference $(FLAGS) +ASFLAGS = $(FLAGS) +LDFLAGS = $(FLAGS) +OBJCOPYFLAGS = -O binary + + +all: $(BIN_DIR) $(OUT_DIR) $(OUT_DIR)/payload-fastdump.bin $(OUT_DIR)/payload-writenvm.bin \ + $(OUT_DIR)/payload-keystoredump.bin + +$(BIN_DIR): + mkdir $(BIN_DIR) +$(OUT_DIR): + mkdir $(OUT_DIR) + +clean: + rm -f $(OUT_DIR)/payload-fastdump.bin $(BIN_DIR)/payload-fastdump.elf $(BIN_DIR)/payload-fastdump.o + rm -f $(OUT_DIR)/payload-writenvm.bin $(BIN_DIR)/payload-writenvm.elf $(BIN_DIR)/payload-writenvm.o + rm -f $(OUT_DIR)/payload-keystoredump.bin $(BIN_DIR)/payload-keystoredump.elf \ + $(BIN_DIR)/payload-keystoredump.o + +$(OUT_DIR)/payload-fastdump.bin: $(BIN_DIR)/payload-fastdump.elf + $(OBJCOPY) $(OBJCOPYFLAGS) $(BIN_DIR)/payload-fastdump.elf $(OUT_DIR)/payload-fastdump.bin +$(BIN_DIR)/payload-fastdump.elf: $(BIN_DIR)/payload-fastdump.o $(CRT0) payload.ld + $(LD) $(LDFLAGS) $(CRT0) $(BIN_DIR)/payload-fastdump.o -T payload.ld -o $(BIN_DIR)/payload-fastdump.elf +$(BIN_DIR)/payload-fastdump.o: payload-fastdump.c + $(CC) -c $(CCFLAGS) $^ -o $@ + +$(OUT_DIR)/payload-writenvm.bin: $(BIN_DIR)/payload-writenvm.elf + $(OBJCOPY) $(OBJCOPYFLAGS) $(BIN_DIR)/payload-writenvm.elf $(OUT_DIR)/payload-writenvm.bin +$(BIN_DIR)/payload-writenvm.elf: $(BIN_DIR)/payload-writenvm.o $(CRT0) payload.ld + $(LD) $(LDFLAGS) $(CRT0) $(BIN_DIR)/payload-writenvm.o -T payload.ld -o $(BIN_DIR)/payload-writenvm.elf +$(BIN_DIR)/payload-writenvm.o: payload-writenvm.c + $(CC) -c $(CCFLAGS) $^ -o $@ + +$(OUT_DIR)/payload-keystoredump.bin: $(BIN_DIR)/payload-keystoredump.elf + $(OBJCOPY) $(OBJCOPYFLAGS) $(BIN_DIR)/payload-keystoredump.elf $(OUT_DIR)/payload-keystoredump.bin +$(BIN_DIR)/payload-keystoredump.elf: $(BIN_DIR)/payload-keystoredump.o $(CRT0) payload.ld + $(LD) $(LDFLAGS) $(CRT0) $(BIN_DIR)/payload-keystoredump.o -T payload.ld -o \ + $(BIN_DIR)/payload-keystoredump.elf +$(BIN_DIR)/payload-keystoredump.o: payload-keystoredump.c + $(CC) -c $(CCFLAGS) $^ -o $@ + +$(CRT0): payload-crt0.S + $(AS) -c $(ASFLAGS) $^ -o $@ diff --git a/payload/payload-crt0.S b/payload/payload-crt0.S new file mode 100644 index 0000000..c1efe7e --- /dev/null +++ b/payload/payload-crt0.S @@ -0,0 +1,71 @@ +.section ".text.crt0" +.syntax unified + + +.globl _start +.thumb +.align 2 +_start: + push {r0-r3} + + // Find our slide value. + adr r0, .mypool + ldr r1, =.mypool + subs r0, r0, r1 + + // Check whether we've relocated already. + ldr r1, =__relocated + adds r1, r1, r0 + ldr r2, [r1] + cmp r2, #0 + bne .already_relocated + movs r2, #1 + str r2, [r1] + + // Zero out the .bss section. + ldr r1, =__bss + adds r1, r1, r0 + ldr r2, =__bss_end + adds r2, r2, r0 + movs r3, #0 +.bss_clear_loop: + cmp r1, r2 + bcs .bss_clear_done + str r3, [r1] + adds r1, r1, #4 + b .bss_clear_loop +.bss_clear_done: + + // Adjust the pointers in the GOT. + ldr r1, =__got + adds r1, r1, r0 + ldr r2, =__got_end + adds r2, r2, r0 +.got_fixup_loop: + cmp r1, r2 + bcs .got_fixup_done + ldr r3, [r1] + adds r3, r3, r0 + str r3, [r1] + adds r1, r1, #4 + b .got_fixup_loop +.got_fixup_done: + +.already_relocated: + // We need a register that isn't r0...r3. r12 is expressly designated + // as destroyable by linker-generated thunk code, so use that. + ldr r1, =payload_start+1 + adds r1, r1, r0 + mov r12, r1 + pop {r0-r3} + bx r12 + +.align 2 +.mypool: +.pool + + +.section ".data" +.align 2 +__relocated: + .word 0 diff --git a/payload/payload-fastdump.c b/payload/payload-fastdump.c new file mode 100644 index 0000000..d526a8f --- /dev/null +++ b/payload/payload-fastdump.c @@ -0,0 +1,115 @@ +#include +#include + +typedef struct PatchRegisterSet_ +{ + uint8_t m_enable; + uint8_t m_dummy[0]; + uint32_t m_target; + uint32_t m_value; + uint32_t m_dummy2; +} PatchRegisterSet; +_Static_assert(sizeof(PatchRegisterSet) == 0x10, "bad PatchRegisterSet type"); +_Static_assert(offsetof(PatchRegisterSet, m_enable) == 0x00, "bad PatchRegisterSet type"); +_Static_assert(offsetof(PatchRegisterSet, m_target) == 0x04, "bad PatchRegisterSet type"); +_Static_assert(offsetof(PatchRegisterSet, m_value) == 0x08, "bad PatchRegisterSet type"); + +typedef struct PatchRegisters_ +{ + PatchRegisterSet m_sets[4]; + uint32_t m_dummy[4]; + uint16_t m_lockControl; +} PatchRegisters; +_Static_assert(offsetof(PatchRegisters, m_sets) == 0x00, "bad PatchRegisters type"); +_Static_assert(offsetof(PatchRegisters, m_lockControl) == 0x50, "bad PatchRegisters type"); + + +static uint32_t DisableInterrupts(); +static void RestoreInterrupts(uint32_t oldCPSR); + + +__attribute__((__target__("thumb"))) +void payload_start(int dummy0, int dummy1, int dummy2, const uint8_t* address) +{ + (void) dummy0; + (void) dummy1; + (void) dummy2; + + // Don't let us messing with the patch registers cause problems. + uint32_t oldCPSR = DisableInterrupts(); + + // Disable patches so that we get a clean dump. + volatile PatchRegisters* patchRegs = (volatile PatchRegisters*) 0x03880000; + patchRegs->m_lockControl = 0xE669; + patchRegs->m_lockControl = 0xCF7E; + + // Save old patch configuration while we disable. + uint8_t patchEnables[4]; + for (int i = 0; i < 4; ++i) + { + patchEnables[i] = patchRegs->m_sets[i].m_enable; + patchRegs->m_sets[i].m_enable = 0x00; + } + + // Copy data to local buffer and checksum. + uint8_t copy_buffer[14]; + unsigned checksum = 0; + for (int i = 0; i < 14; ++i) + { + uint8_t b = address[i]; + copy_buffer[i] = b; + checksum += b; + } + + // The address itself is part of the checksum. + for (int i = 0; i < sizeof(uint32_t); ++i) + { + checksum += (uint8_t) (((uintptr_t) address) >> (i * 8)); + } + + // Send a response code, checksum, and the 14 bytes back. + volatile uint8_t* out_reg = (volatile uint8_t*) 0x036000D9; + *out_reg = 0x69; + *out_reg = (uint8_t) ~checksum; + + for (int i = 0; i < 14; ++i) + { + *out_reg = copy_buffer[i]; + } + + // Restore patch configuration. + for (int i = 0; i < 4; ++i) + { + patchRegs->m_sets[i].m_enable = patchEnables[i]; + } + patchRegs->m_lockControl = 0x78C7; + patchRegs->m_lockControl = 0xF6D7; + + // Restore CPSR before returning. + RestoreInterrupts(oldCPSR); +} + +__attribute__((__noinline__, __target__("arm"))) +static uint32_t DisableInterrupts() +{ + uint32_t oldCPSR; + __asm__( + "mrs %0, CPSR\n\t" + : "=r"(oldCPSR)); + + __asm__ volatile( + "msr CPSR_c, %0\n\t" + : + : "r"(oldCPSR | 0x000000C0)); + + return oldCPSR; +} + +__attribute__((__noinline__, __target__("arm"))) +static void RestoreInterrupts(uint32_t oldCPSR) +{ + __asm__ volatile( + "msr CPSR_c, %0\n\t" + : + : "r"(oldCPSR)); +} diff --git a/payload/payload-keystoredump.c b/payload/payload-keystoredump.c new file mode 100644 index 0000000..86e571f --- /dev/null +++ b/payload/payload-keystoredump.c @@ -0,0 +1,49 @@ +#include +#include + + +__attribute__((__target__("thumb"))) +void payload_start(int dummy0, int dummy1, int dummy2, uint32_t parameter) +{ + union + { + uint8_t m_bytes[8]; + uint16_t m_halfWords[4]; + } keyData; + + // Unlock the keystore mechanism. + volatile uint8_t* unlockReg1 = (volatile uint8_t*) 0x94255ABC; + volatile uint8_t* unlockReg2 = (volatile uint8_t*) 0x94255EF0; + volatile uint8_t* unlockReg3 = (volatile uint8_t*) 0xFFFFF234; + volatile uint8_t* unlockReg4 = (volatile uint8_t*) 0xFFFFF678; + + *unlockReg1 = 0xC9; + *unlockReg2 = 0xCB; + *unlockReg3 = 0x5D; + *unlockReg4 = 0x58; + + // Select key to use. + volatile uint16_t* keystoreOffset = (volatile uint16_t*) 0x03004F00; + *keystoreOffset = (uint16_t) parameter; + + // Read key out. + volatile uint16_t* keystoreData = (volatile uint16_t*) 0x03004F08; + keyData.m_halfWords[0] = *keystoreData; + keyData.m_halfWords[1] = *keystoreData; + keyData.m_halfWords[2] = *keystoreData; + keyData.m_halfWords[3] = *keystoreData; + + // Lock the keystore mechanism. + *unlockReg1 = 0x9C; + *unlockReg2 = 0xBC; + *unlockReg3 = 0xD5; + *unlockReg4 = 0x85; + + // Return the result. + volatile uint8_t* scmdReply = (volatile uint8_t*) 0x036000D9; + *scmdReply = 0x00; + for (int i = 0; i < sizeof(keyData.m_bytes); ++i) + { + *scmdReply = keyData.m_bytes[i]; + } +} diff --git a/payload/payload-writenvm.c b/payload/payload-writenvm.c new file mode 100644 index 0000000..3c9a55e --- /dev/null +++ b/payload/payload-writenvm.c @@ -0,0 +1,92 @@ +#include +#include + + +enum +{ + MECHACON_ROM_START = 0x00000000, + MECHACON_ROM_SIZE = 0x44000, + MECHACON_EEPROM_SIZE = 0x400, +}; + + +// Address of the WriteNVM SCMD handler in ROM, saved for efficiency. +int (*scmd_0B_WriteNVM)(unsigned wordOffset, uint16_t value) = (int (*)(unsigned, uint16_t)) -1; + + +// The prolog code of WriteNVM, so we can find it in any version. +static const union +{ + uint8_t m_bytes[12]; + uint32_t m_words[3]; +} c_pattern_WriteNVM = { + { + 0xF0, 0xB5, // push {r4-r7,lr} + 0x05, 0x1C, // movs r5, r0 + 0x0E, 0x1C, // movs r6, r1 + 0x01, 0x27, // movs r7, #1 + 0x00, 0x21, // movs r1, #0 + 0x01, 0x20, // movs r0, #1 + } +}; + + +__attribute__((__target__("thumb"))) +void payload_start(int dummy0, int dummy1, int dummy2, uint32_t parameter) +{ + volatile uint8_t* scmdReply = (volatile uint8_t*) 0x036000D9; + + // Search for WriteNVM in ROM if we haven't yet. + if (scmd_0B_WriteNVM == (int (*)(unsigned, uint16_t)) -1) + { + // NOTE: This code needs the -fno-isolate-erroneous-paths-dereference + // GCC command line parameter, because otherwise it will assume that + // reading null will crash and skip this code. + const uint32_t* romEnd = (const uint32_t*) (MECHACON_ROM_START + + MECHACON_ROM_SIZE - sizeof(c_pattern_WriteNVM)); + const uint32_t* search; + uint32_t firstWord = c_pattern_WriteNVM.m_words[0]; + for (search = (const uint32_t*) MECHACON_ROM_START; search <= romEnd; ++search) + { + if (*search == firstWord) + { + if ((search[1] == c_pattern_WriteNVM.m_words[1]) && + (search[2] == c_pattern_WriteNVM.m_words[2])) + { + goto Found; + } + } + } + + *scmdReply = 0x86; + return; + + Found: + // Don't forget to OR with 1 because it's a Thumb function. + scmd_0B_WriteNVM = (int (*)(unsigned, uint16_t)) (((uintptr_t) search) | 1); + } + + // Act the same way as the real WriteNVM: use a big-endian address + // and value. The back door doesn't know that the parameter is bytes + // rather than a uint32_t, so put it back and parse it that way. + union + { + uint32_t m_word; + uint8_t m_bytes[4]; + } converter; + converter.m_word = parameter; + + unsigned wordOffset = (((unsigned) converter.m_bytes[0]) << 8) + converter.m_bytes[1]; + uint16_t value = (uint16_t) ((((unsigned) converter.m_bytes[2]) << 8) + converter.m_bytes[3]); + + // Address out of range? + if (wordOffset > MECHACON_EEPROM_SIZE / sizeof(uint16_t)) + { + *scmdReply = 0x80; + return; + } + + // Execute and return the result. + int result = (*scmd_0B_WriteNVM)(wordOffset, value); + *scmdReply = (uint8_t) result; +} diff --git a/payload/payload.ld b/payload/payload.ld new file mode 100644 index 0000000..25768df --- /dev/null +++ b/payload/payload.ld @@ -0,0 +1,45 @@ +OUTPUT_FORMAT("elf32-littlearm") +ENTRY(_start) +__phys = 0x00010000; +SECTIONS +{ + .text __phys : AT(__phys) { + __code = .; + *(.text.crt0) + *(.text) + . = ALIGN(4); + } + .rodata : AT(__phys + (__rodata - __code)) + { + __rodata = .; + *(.rodata) + . = ALIGN(4); + } + .data : AT(__phys + (__data - __code)) + { + __data = .; + *(.data) + . = ALIGN(4); + } + .got : AT(__phys + (__got - __code)) + { + __got = .; + *(.got) + . = ALIGN(4); + __got_end = .; + } + .got.plt : AT(__phys + (__got_plt - __code)) + { + __got_plt = .; + *(.got.plt) + . = ALIGN(4); + } + .bss : AT(__phys + (__bss - __code)) + { + __bss = .; + *(.bss) + . = ALIGN(4); + __bss_end = .; + } + __end = .; +}