├── COMPILING ├── KindleTool.xcodeproj └── project.pbxproj ├── KindleTool ├── KindleTool.1 ├── Makefile ├── convert.c ├── create.c ├── kindle_tool.c └── kindle_tool.h ├── Makefile └── README /COMPILING: -------------------------------------------------------------------------------- 1 | Recommended Compilation Directions 2 | 3 | To compile for Linux: 4 | 1) Install the dependency packages: libtar-dev, libssl-dev, zlib1g-dev 5 | 2) Compile using "make" in the tool's directory 6 | 7 | To compile for OSX: 8 | 1) Get MacPorts 9 | 2) Install the packages: libtar, openssl, zlib 10 | 3) Open the Xcode 4 project and compile 11 | 12 | To compile for Windows: 13 | 1) Get Cygwin 14 | 2) Install the packages: openssl-devel, zlib-devel 15 | 3) Compile and install libtar 16 | 4) Compile using "make" in the tool's directory 17 | -------------------------------------------------------------------------------- /KindleTool.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE1DABEC14AF9C1E003B5CBA /* create.c in Sources */ = {isa = PBXBuildFile; fileRef = CE1DABEB14AF9C1E003B5CBA /* create.c */; }; 11 | CEE4226814589F0C005E216E /* kindle_tool.c in Sources */ = {isa = PBXBuildFile; fileRef = CEE4226714589F0C005E216E /* kindle_tool.c */; }; 12 | CEE4226A14589F0C005E216E /* KindleTool.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = CEE4226914589F0C005E216E /* KindleTool.1 */; }; 13 | CEE42277145B818D005E216E /* convert.c in Sources */ = {isa = PBXBuildFile; fileRef = CEE42276145B818D005E216E /* convert.c */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | CEE4226114589F0C005E216E /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | CEE4226A14589F0C005E216E /* KindleTool.1 in CopyFiles */, 24 | ); 25 | runOnlyForDeploymentPostprocessing = 1; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | CE1DABEB14AF9C1E003B5CBA /* create.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = create.c; sourceTree = ""; }; 31 | CEE4226314589F0C005E216E /* KindleTool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KindleTool; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | CEE4226714589F0C005E216E /* kindle_tool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = kindle_tool.c; sourceTree = ""; }; 33 | CEE4226914589F0C005E216E /* KindleTool.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = KindleTool.1; sourceTree = ""; }; 34 | CEE42276145B818D005E216E /* convert.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = convert.c; sourceTree = ""; }; 35 | CEE42278145B82E0005E216E /* kindle_tool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = kindle_tool.h; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | CEE4226014589F0C005E216E /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | CEE4225814589F0C005E216E = { 50 | isa = PBXGroup; 51 | children = ( 52 | CEE4226614589F0C005E216E /* KindleTool */, 53 | CEE4226414589F0C005E216E /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | CEE4226414589F0C005E216E /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | CEE4226314589F0C005E216E /* KindleTool */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | CEE4226614589F0C005E216E /* KindleTool */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | CEE42276145B818D005E216E /* convert.c */, 69 | CE1DABEB14AF9C1E003B5CBA /* create.c */, 70 | CEE42278145B82E0005E216E /* kindle_tool.h */, 71 | CEE4226714589F0C005E216E /* kindle_tool.c */, 72 | CEE4226914589F0C005E216E /* KindleTool.1 */, 73 | ); 74 | path = KindleTool; 75 | sourceTree = ""; 76 | }; 77 | /* End PBXGroup section */ 78 | 79 | /* Begin PBXNativeTarget section */ 80 | CEE4226214589F0C005E216E /* KindleTool */ = { 81 | isa = PBXNativeTarget; 82 | buildConfigurationList = CEE4226D14589F0C005E216E /* Build configuration list for PBXNativeTarget "KindleTool" */; 83 | buildPhases = ( 84 | CEE4225F14589F0C005E216E /* Sources */, 85 | CEE4226014589F0C005E216E /* Frameworks */, 86 | CEE4226114589F0C005E216E /* CopyFiles */, 87 | ); 88 | buildRules = ( 89 | ); 90 | dependencies = ( 91 | ); 92 | name = KindleTool; 93 | productName = KindleTool; 94 | productReference = CEE4226314589F0C005E216E /* KindleTool */; 95 | productType = "com.apple.product-type.tool"; 96 | }; 97 | /* End PBXNativeTarget section */ 98 | 99 | /* Begin PBXProject section */ 100 | CEE4225A14589F0C005E216E /* Project object */ = { 101 | isa = PBXProject; 102 | attributes = { 103 | LastUpgradeCheck = 0420; 104 | }; 105 | buildConfigurationList = CEE4225D14589F0C005E216E /* Build configuration list for PBXProject "KindleTool" */; 106 | compatibilityVersion = "Xcode 3.2"; 107 | developmentRegion = English; 108 | hasScannedForEncodings = 0; 109 | knownRegions = ( 110 | en, 111 | ); 112 | mainGroup = CEE4225814589F0C005E216E; 113 | productRefGroup = CEE4226414589F0C005E216E /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | CEE4226214589F0C005E216E /* KindleTool */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXSourcesBuildPhase section */ 123 | CEE4225F14589F0C005E216E /* Sources */ = { 124 | isa = PBXSourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | CEE4226814589F0C005E216E /* kindle_tool.c in Sources */, 128 | CEE42277145B818D005E216E /* convert.c in Sources */, 129 | CE1DABEC14AF9C1E003B5CBA /* create.c in Sources */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXSourcesBuildPhase section */ 134 | 135 | /* Begin XCBuildConfiguration section */ 136 | CEE4226B14589F0C005E216E /* Debug */ = { 137 | isa = XCBuildConfiguration; 138 | buildSettings = { 139 | ALWAYS_SEARCH_USER_PATHS = NO; 140 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 141 | CLANG_ENABLE_OBJC_ARC = YES; 142 | COPY_PHASE_STRIP = NO; 143 | GCC_C_LANGUAGE_STANDARD = gnu99; 144 | GCC_DYNAMIC_NO_PIC = NO; 145 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 146 | GCC_OPTIMIZATION_LEVEL = 0; 147 | GCC_PREPROCESSOR_DEFINITIONS = ( 148 | "DEBUG=1", 149 | "$(inherited)", 150 | ); 151 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 152 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 153 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 154 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 155 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 156 | GCC_WARN_UNUSED_VARIABLE = YES; 157 | HEADER_SEARCH_PATHS = /opt/local/include; 158 | LIBRARY_SEARCH_PATHS = /opt/local/lib; 159 | MACOSX_DEPLOYMENT_TARGET = 10.7; 160 | ONLY_ACTIVE_ARCH = YES; 161 | OTHER_LDFLAGS = ( 162 | "-ltar", 163 | "-lcrypto", 164 | "-lz", 165 | ); 166 | SDKROOT = macosx; 167 | }; 168 | name = Debug; 169 | }; 170 | CEE4226C14589F0C005E216E /* Release */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 175 | CLANG_ENABLE_OBJC_ARC = YES; 176 | COPY_PHASE_STRIP = YES; 177 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 178 | GCC_C_LANGUAGE_STANDARD = gnu99; 179 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 180 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 181 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 182 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 183 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 184 | GCC_WARN_UNUSED_VARIABLE = YES; 185 | HEADER_SEARCH_PATHS = /opt/local/include; 186 | LIBRARY_SEARCH_PATHS = /opt/local/lib; 187 | MACOSX_DEPLOYMENT_TARGET = 10.7; 188 | OTHER_LDFLAGS = ( 189 | "-ltar", 190 | "-lcrypto", 191 | "-lz", 192 | ); 193 | SDKROOT = macosx; 194 | }; 195 | name = Release; 196 | }; 197 | CEE4226E14589F0C005E216E /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | PRODUCT_NAME = "$(TARGET_NAME)"; 201 | }; 202 | name = Debug; 203 | }; 204 | CEE4226F14589F0C005E216E /* Release */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | PRODUCT_NAME = "$(TARGET_NAME)"; 208 | }; 209 | name = Release; 210 | }; 211 | /* End XCBuildConfiguration section */ 212 | 213 | /* Begin XCConfigurationList section */ 214 | CEE4225D14589F0C005E216E /* Build configuration list for PBXProject "KindleTool" */ = { 215 | isa = XCConfigurationList; 216 | buildConfigurations = ( 217 | CEE4226B14589F0C005E216E /* Debug */, 218 | CEE4226C14589F0C005E216E /* Release */, 219 | ); 220 | defaultConfigurationIsVisible = 0; 221 | defaultConfigurationName = Release; 222 | }; 223 | CEE4226D14589F0C005E216E /* Build configuration list for PBXNativeTarget "KindleTool" */ = { 224 | isa = XCConfigurationList; 225 | buildConfigurations = ( 226 | CEE4226E14589F0C005E216E /* Debug */, 227 | CEE4226F14589F0C005E216E /* Release */, 228 | ); 229 | defaultConfigurationIsVisible = 0; 230 | defaultConfigurationName = Release; 231 | }; 232 | /* End XCConfigurationList section */ 233 | }; 234 | rootObject = CEE4225A14589F0C005E216E /* Project object */; 235 | } 236 | -------------------------------------------------------------------------------- /KindleTool/KindleTool.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 10/26/11 \" DATE 7 | .Dt KindleTool 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm KindleTool, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /KindleTool/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | LD=gcc 3 | CFLAGS= 4 | LDFLAGS= 5 | INCLUDES=-Llib -Iincludes 6 | LIBS=-lcrypto -ltar -lz 7 | DEPS=kindle_tool.h 8 | OBJ=kindle_tool.o create.o convert.o 9 | 10 | %.o: %.c $(DEPS) 11 | $(CC) $(INCLUDES) -c -o $@ $< $(CFLAGS) 12 | 13 | kindletool: $(OBJ) 14 | $(LD) -o $@ $^ $(LDFLAGS) $(INCLUDES) $(LIBS) 15 | 16 | clean: 17 | rm -f *.o kindletool 18 | -------------------------------------------------------------------------------- /KindleTool/convert.c: -------------------------------------------------------------------------------- 1 | // 2 | // extract.c 3 | // KindleTool 4 | // 5 | // Copyright (C) 2011 Yifan Lu 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #include "kindle_tool.h" 22 | 23 | FILE *gunzip_file(FILE *input) 24 | { 25 | FILE *output; 26 | gzFile gz_input; 27 | unsigned char buffer[BUFFER_SIZE]; 28 | size_t count; 29 | 30 | // create a temporary file and open 31 | if((output = tmpfile()) == NULL) 32 | { 33 | fprintf(stderr, "Cannot create gunzip output.\n"); 34 | return NULL; 35 | } 36 | // open the gzip file 37 | if((gz_input = gzdopen(fileno(input), "rb")) == NULL) 38 | { 39 | fprintf(stderr, "Cannot read compressed input.\n"); 40 | return NULL; 41 | } 42 | // read the input and decompress it 43 | while((count = (uint32_t)gzread(gz_input, buffer, BUFFER_SIZE)) > 0) 44 | { 45 | if(fwrite(buffer, sizeof(char), BUFFER_SIZE, output) != count) 46 | { 47 | fprintf(stderr, "Cannot decompress input.\n"); 48 | gzclose(gz_input); 49 | return NULL; 50 | } 51 | } 52 | gzclose(gz_input); 53 | rewind(output); 54 | return output; 55 | } 56 | 57 | int kindle_read_bundle_header(UpdateHeader *header, FILE *input) 58 | { 59 | if(fread(header, sizeof(char), MAGIC_NUMBER_LENGTH, input) < 1 || ferror(input) != 0) 60 | { 61 | return -1; 62 | } 63 | return 0; 64 | } 65 | 66 | int kindle_convert(FILE *input, FILE *output, FILE *sig_output) 67 | { 68 | UpdateHeader header; 69 | BundleVersion bundle_version; 70 | if(kindle_read_bundle_header(&header, input) < 0) 71 | { 72 | fprintf(stderr, "Cannot read input file.\n"); 73 | return -1; 74 | } 75 | fprintf(stderr, "Bundle %s\n", header.magic_number); 76 | bundle_version = get_bundle_version(header.magic_number); 77 | switch(bundle_version) 78 | { 79 | case OTAUpdateV2: 80 | fprintf(stderr, "Bundle Type %s\n", "OTA V2"); 81 | return kindle_convert_ota_update_v2(input, output); // no absolutet size, so no struct to pass 82 | break; 83 | case UpdateSignature: 84 | if(kindle_convert_signature(&header, input, sig_output) < 0) 85 | { 86 | fprintf(stderr, "Cannot extract signature file!\n"); 87 | return -1; 88 | } 89 | return kindle_convert(input, output, sig_output); 90 | break; 91 | case OTAUpdate: 92 | fprintf(stderr, "Bundle Type %s\n", "OTA V1"); 93 | return kindle_convert_ota_update(&header, input, output); 94 | break; 95 | case RecoveryUpdate: 96 | fprintf(stderr, "Bundle Type %s\n", "Recovery"); 97 | return kindle_convert_recovery(&header, input, output); 98 | break; 99 | case UnknownUpdate: 100 | default: 101 | fprintf(stderr, "Unknown update bundle version!\n"); 102 | break; 103 | } 104 | return -1; // if we get here, there has been an error 105 | } 106 | 107 | int kindle_convert_ota_update_v2(FILE *input, FILE *output) 108 | { 109 | char *data; 110 | int index; 111 | uint64_t source_revision; 112 | uint64_t target_revision; 113 | uint16_t num_devices; 114 | uint16_t device; 115 | //uint16_t *devices; 116 | uint16_t critical; 117 | char *md5_sum; 118 | uint16_t num_metadata; 119 | uint16_t metastring_length; 120 | char *metastring; 121 | //unsigned char **metastrings; 122 | 123 | // First read the set block size and determine how much to resize 124 | data = malloc(OTA_UPDATE_V2_BLOCK_SIZE * sizeof(char)); 125 | fread(data, sizeof(char), OTA_UPDATE_V2_BLOCK_SIZE, input); 126 | index = 0; 127 | 128 | source_revision = *(uint64_t *)&data[index]; 129 | index += sizeof(uint64_t); 130 | fprintf(stderr, "Minimum OTA %llu\n", source_revision); 131 | target_revision = *(uint64_t *)&data[index]; 132 | index += sizeof(uint64_t); 133 | fprintf(stderr, "Target OTA %llu\n", target_revision); 134 | num_devices = *(uint16_t *)&data[index]; 135 | index += sizeof(uint16_t); 136 | fprintf(stderr, "Devices %hd\n", num_devices); 137 | free(data); 138 | 139 | // Now get the data 140 | data = malloc(num_devices * sizeof(uint16_t)); 141 | fread(data, sizeof(uint16_t), num_devices, input); 142 | for(index = 0; index < num_devices * sizeof(uint16_t); index += sizeof(uint16_t)) 143 | { 144 | device = *(uint16_t *)&data[index]; 145 | fprintf(stderr, "Device %s\n", convert_device_id(device)); 146 | } 147 | free(data); 148 | 149 | // Now get second part of set sized data 150 | data = malloc(OTA_UPDATE_V2_PART_2_BLOCK_SIZE * sizeof(char)); 151 | fread(data, sizeof(char), OTA_UPDATE_V2_PART_2_BLOCK_SIZE, input); 152 | index = 0; 153 | 154 | critical = *(uint16_t *)&data[index]; 155 | index += sizeof(uint16_t); 156 | fprintf(stderr, "Critical %hd\n", critical); 157 | md5_sum = &data[index]; 158 | dm((unsigned char*)md5_sum, MD5_HASH_LENGTH); 159 | index += MD5_HASH_LENGTH; 160 | fprintf(stderr, "MD5 Hash %.*s\n", MD5_HASH_LENGTH, md5_sum); 161 | num_metadata = *(uint16_t *)&data[index]; 162 | index += sizeof(uint16_t); 163 | fprintf(stderr, "Metadata %hd\n", num_metadata); 164 | free(data); 165 | 166 | // Finally, get the metastrings 167 | for(index = 0; index < num_metadata; index++) 168 | { 169 | fread(&metastring_length, sizeof(uint16_t), 1, input); 170 | metastring = malloc(metastring_length); 171 | fread(metastring, sizeof(char), metastring_length, input); 172 | fprintf(stderr, "Metastring %.*s\n", metastring_length, metastring); 173 | free(metastring); 174 | } 175 | 176 | if(ferror(input) != 0) 177 | { 178 | fprintf(stderr, "Cannot read update correctly.\n"); 179 | return -1; 180 | } 181 | 182 | if(output == NULL) 183 | { 184 | return 0; 185 | } 186 | 187 | // Now we can decrypt the data 188 | return demunger(input, output, 0); 189 | } 190 | 191 | int kindle_convert_signature(UpdateHeader *header, FILE *input, FILE *output) 192 | { 193 | CertificateNumber cert_num; 194 | char *cert_name; 195 | size_t seek; 196 | unsigned char *signature; 197 | 198 | 199 | if(fread(header->data.signature_header_data, sizeof(char), UPDATE_SIGNATURE_BLOCK_SIZE, input) < UPDATE_SIGNATURE_BLOCK_SIZE) 200 | { 201 | fprintf(stderr, "Cannot read signature header.\n"); 202 | return -1; 203 | } 204 | cert_num = (CertificateNumber)(header->data.signature.certificate_number); 205 | fprintf(stderr, "Cert number %u\n", cert_num); 206 | switch(cert_num) 207 | { 208 | case CertificateDeveloper: 209 | cert_name = "pubdevkey01.pem"; 210 | seek = CERTIFICATE_DEV_SIZE; 211 | break; 212 | case Certificate1K: 213 | cert_name = "pubprodkey01.pem"; 214 | seek = CERTIFICATE_1K_SIZE; 215 | break; 216 | case Certificate2K: 217 | cert_name = "pubprodkey02.pem"; 218 | seek = CERTIFICATE_2K_SIZE; 219 | break; 220 | case CertificateUnknown: 221 | default: 222 | fprintf(stderr, "Unknown signature size, cannot continue.\n"); 223 | return -1; 224 | break; 225 | } 226 | fprintf(stderr, "Cert file %s\n", cert_name); 227 | if(output == NULL) 228 | { 229 | return fseek(input, seek, SEEK_CUR); 230 | } 231 | else 232 | { 233 | signature = malloc(seek); 234 | if(fread(signature, sizeof(char), seek, input) < seek) 235 | { 236 | fprintf(stderr, "Cannot read signature!\n"); 237 | free(signature); 238 | return -1; 239 | } 240 | if(fwrite(signature, sizeof(char), seek, output) < seek) 241 | { 242 | fprintf(stderr, "Cannot write signature file!\n"); 243 | free(signature); 244 | return -1; 245 | } 246 | } 247 | return 0; 248 | } 249 | 250 | int kindle_convert_ota_update(UpdateHeader *header, FILE *input, FILE *output) 251 | { 252 | if(fread(header->data.ota_header_data, sizeof(char), OTA_UPDATE_BLOCK_SIZE, input) < OTA_UPDATE_BLOCK_SIZE) 253 | { 254 | fprintf(stderr, "Cannot read OTA header.\n"); 255 | return -1; 256 | } 257 | dm((unsigned char*)header->data.ota_update.md5_sum, MD5_HASH_LENGTH); 258 | fprintf(stderr, "MD5 Hash %.*s\n", MD5_HASH_LENGTH, header->data.ota_update.md5_sum); 259 | fprintf(stderr, "Minimum OTA %d\n", header->data.ota_update.source_revision); 260 | fprintf(stderr, "Target OTA %d\n", header->data.ota_update.target_revision); 261 | fprintf(stderr, "Device %s\n", convert_device_id(header->data.ota_update.device)); 262 | fprintf(stderr, "Optional %d\n", header->data.ota_update.optional); 263 | 264 | if(output == NULL) 265 | { 266 | return 0; 267 | } 268 | 269 | return demunger(input, output, 0); 270 | } 271 | 272 | int kindle_convert_recovery(UpdateHeader *header, FILE *input, FILE *output) 273 | { 274 | if(fread(header->data.recovery_header_data, sizeof(char), RECOVERY_UPDATE_BLOCK_SIZE, input) < RECOVERY_UPDATE_BLOCK_SIZE) 275 | { 276 | fprintf(stderr, "Cannot read recovery update header.\n"); 277 | return -1; 278 | } 279 | dm((unsigned char*)header->data.recovery_update.md5_sum, MD5_HASH_LENGTH); 280 | fprintf(stderr, "MD5 Hash %.*s\n", MD5_HASH_LENGTH, header->data.recovery_update.md5_sum); 281 | fprintf(stderr, "Magic 1 %d\n", header->data.recovery_update.magic_1); 282 | fprintf(stderr, "Magic 2 %d\n", header->data.recovery_update.magic_2); 283 | fprintf(stderr, "Minor %d\n", header->data.recovery_update.minor); 284 | fprintf(stderr, "Device %s\n", convert_device_id(header->data.recovery_update.device)); 285 | 286 | if(output == NULL) 287 | { 288 | return 0; 289 | } 290 | 291 | return demunger(input, output, 0); 292 | } 293 | 294 | int kindle_convert_main(int argc, char *argv[]) 295 | { 296 | int opt; 297 | int opt_index; 298 | static const struct option opts[] = { 299 | { "stdout", no_argument, NULL, 'c' }, 300 | { "info", no_argument, NULL, 'i' }, 301 | { "sig", required_argument, NULL, 's' } 302 | }; 303 | FILE *input; 304 | FILE *output; 305 | FILE *sig_output; 306 | const char *in_name; 307 | char *out_name; 308 | int info_only; 309 | 310 | sig_output = NULL; 311 | out_name = NULL; 312 | output = NULL; 313 | info_only = 0; 314 | optind = -1; // hack to get around the fact that we skipped some arguments 315 | while((opt = getopt_long(argc, argv, "ics:", opts, &opt_index)) != -1) 316 | { 317 | switch(opt) 318 | { 319 | case 'i': 320 | info_only = 1; 321 | break; 322 | case 'c': 323 | output = stdout; 324 | break; 325 | case 's': 326 | if((sig_output = fopen(optarg, "wb")) == NULL) 327 | { 328 | fprintf(stderr, "Cannot open signature output for writing.\n"); 329 | free(out_name); 330 | return -1; 331 | } 332 | break; 333 | default: 334 | break; 335 | } 336 | } 337 | if(argc < 1) 338 | { 339 | fprintf(stderr, "No input specified.\n"); 340 | fclose(sig_output); 341 | return -1; 342 | } 343 | argc -= (optind-1); argv += optind; // next argument 344 | in_name = argv[0]; 345 | if(!info_only && output == NULL) // not info AND not stdout 346 | { 347 | out_name = malloc(strlen(in_name) + 7); 348 | strcpy(out_name, in_name); 349 | strcat(out_name, ".tar.gz"); 350 | if((output = fopen(out_name, "wb")) == NULL) 351 | { 352 | fprintf(stderr, "Cannot open output for writing.\n"); 353 | free(out_name); 354 | fclose(sig_output); 355 | return -1; 356 | } 357 | } 358 | if((input = fopen(in_name, "rb")) == NULL) 359 | { 360 | fprintf(stderr, "Cannot open input for reading.\n"); 361 | free(out_name); 362 | fclose(sig_output); 363 | fclose(output); 364 | return -1; 365 | } 366 | if(kindle_convert(input, output, sig_output) < 0) 367 | { 368 | fprintf(stderr, "Error converting update.\n"); 369 | remove(out_name); // clean up our mess 370 | free(out_name); 371 | fclose(sig_output); 372 | fclose(output); 373 | fclose(input); 374 | return -1; 375 | } 376 | if(output != stdout && !info_only) // if output was some file, delete the original 377 | remove(in_name); 378 | free(out_name); 379 | fclose(sig_output); 380 | fclose(output); 381 | fclose(input); 382 | return 0; 383 | } 384 | 385 | int kindle_extract_main(int argc, char *argv[]) 386 | { 387 | FILE *bin_input; 388 | FILE *gz_output; 389 | FILE *tar_input; 390 | TAR *tar; 391 | 392 | if(argc < 2) 393 | { 394 | fprintf(stderr, "Invalid number of arguments.\n"); 395 | return -1; 396 | } 397 | if((bin_input = fopen(argv[0], "rb")) == NULL) 398 | { 399 | fprintf(stderr, "Cannot open update input.\n"); 400 | return -1; 401 | } 402 | if((gz_output = tmpfile()) == NULL) 403 | { 404 | fprintf(stderr, "Cannot create temporary file.\n"); 405 | fclose(bin_input); 406 | return -1; 407 | } 408 | if(kindle_convert(bin_input, gz_output, NULL) < 0) 409 | { 410 | fprintf(stderr, "Error converting update.\n"); 411 | fclose(bin_input); 412 | fclose(gz_output); 413 | return -1; 414 | } 415 | rewind(gz_output); 416 | if((tar_input = gunzip_file(gz_output)) == NULL) 417 | { 418 | fprintf(stderr, "Error decompressing update.\n"); 419 | fclose(bin_input); 420 | fclose(gz_output); 421 | return -1; 422 | } 423 | if(tar_fdopen(&tar, fileno(tar_input), NULL, NULL, O_RDONLY, 0644, TAR_GNU) < 0) 424 | { 425 | fprintf(stderr, "Error opening update tar.\n"); 426 | fclose(bin_input); 427 | fclose(gz_output); 428 | fclose(tar_input); 429 | return -1; 430 | } 431 | if(tar_extract_all(tar, argv[1]) < 0) 432 | { 433 | fprintf(stderr, "Error extracting tar.\n"); 434 | tar_close(tar); 435 | fclose(bin_input); 436 | fclose(gz_output); 437 | fclose(tar_input); 438 | return -1; 439 | } 440 | tar_close(tar); 441 | fclose(bin_input); 442 | fclose(gz_output); 443 | fclose(tar_input); 444 | return 0; 445 | } 446 | -------------------------------------------------------------------------------- /KindleTool/create.c: -------------------------------------------------------------------------------- 1 | // 2 | // create.c 3 | // KindleTool 4 | // 5 | // Copyright (C) 2011 Yifan Lu 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #include "kindle_tool.h" 22 | 23 | int sign_file(FILE *in_file, RSA *rsa_pkey, FILE *sigout_file) 24 | { 25 | /* Taken from: http://stackoverflow.com/a/2054412/91422 */ 26 | EVP_PKEY *pkey; 27 | EVP_MD_CTX ctx; 28 | unsigned char buffer[BUFFER_SIZE]; 29 | size_t len; 30 | unsigned char *sig; 31 | uint32_t siglen; 32 | pkey = EVP_PKEY_new(); 33 | 34 | if(EVP_PKEY_set1_RSA(pkey, rsa_pkey) == 0) 35 | { 36 | fprintf(stderr, "EVP_PKEY_assign_RSA: failed.\n"); 37 | return -2; 38 | } 39 | EVP_MD_CTX_init(&ctx); 40 | if(!EVP_SignInit(&ctx, EVP_sha256())) 41 | { 42 | fprintf(stderr, "EVP_SignInit: failed.\n"); 43 | EVP_PKEY_free(pkey); 44 | return -3; 45 | } 46 | while((len = fread(buffer, sizeof(char), BUFFER_SIZE, in_file)) > 0) 47 | { 48 | if (!EVP_SignUpdate(&ctx, buffer, len)) 49 | { 50 | fprintf(stderr, "EVP_SignUpdate: failed.\n"); 51 | EVP_PKEY_free(pkey); 52 | return -4; 53 | } 54 | } 55 | if(ferror(in_file)) 56 | { 57 | fprintf(stderr, "Error reading file.\n"); 58 | EVP_PKEY_free(pkey); 59 | return -5; 60 | } 61 | sig = malloc(EVP_PKEY_size(pkey)); 62 | if(!EVP_SignFinal(&ctx, sig, &siglen, pkey)) 63 | { 64 | fprintf(stderr, "EVP_SignFinal: failed.\n"); 65 | free(sig); 66 | EVP_PKEY_free(pkey); 67 | return -6; 68 | } 69 | 70 | if(fwrite(sig, sizeof(char), siglen, sigout_file) < siglen) 71 | { 72 | fprintf(stderr, "Error writing signature file.\n"); 73 | free(sig); 74 | EVP_PKEY_free(pkey); 75 | return -7; 76 | } 77 | 78 | free(sig); 79 | EVP_PKEY_free(pkey); 80 | return 0; 81 | } 82 | 83 | FILE *gzip_file(FILE *input) 84 | { 85 | gzFile gz_file; 86 | unsigned char buffer[BUFFER_SIZE]; 87 | size_t count; 88 | FILE *gz_input; 89 | 90 | // create a temporary file and open it in gzip 91 | if((gz_input = tmpfile()) == NULL || (gz_file = gzdopen(fileno(gz_input), "wb")) == NULL) 92 | { 93 | fprintf(stderr, "Cannot create temporary file to compress input.\n"); 94 | return NULL; 95 | } 96 | // read the input and compress it 97 | while((count = fread(buffer, sizeof(char), BUFFER_SIZE, input)) > 0) 98 | { 99 | if(gzwrite(gz_file, buffer, (uint32_t)count) != count) 100 | { 101 | fprintf(stderr, "Cannot compress input.\n"); 102 | gzclose(gz_file); 103 | return NULL; 104 | } 105 | } 106 | if(ferror(input) != 0) 107 | { 108 | fprintf(stderr, "Error reading input.\n"); 109 | gzclose(gz_file); 110 | return NULL; 111 | } 112 | gzflush(gz_file, Z_FINISH); // we cannot gzclose yet, or temp file is deleted 113 | // move the file pointer back to the beginning 114 | rewind(gz_input); 115 | return gz_input; 116 | } 117 | 118 | int kindle_create_tar_from_directory(const char *path, FILE *tar_out, RSA *rsa_pkey) 119 | { 120 | char temp_index[L_tmpnam]; 121 | char temp_index_sig[L_tmpnam]; 122 | char *cwd; 123 | DIR *dir; 124 | FILE *index_file; 125 | FILE *index_sig_file; 126 | TAR *tar; 127 | 128 | // save current directory 129 | cwd = getcwd(NULL, 0); 130 | // move to new directory 131 | if(chdir(path) < 0) 132 | { 133 | fprintf(stderr, "Cannot access input directory.\n"); 134 | chdir((const char*)cwd); 135 | return -1; 136 | } 137 | if((dir = opendir(".")) == NULL) 138 | { 139 | fprintf(stderr, "Cannot access input directory.\n"); 140 | chdir((const char*)cwd); 141 | return -1; 142 | } 143 | // create index file 144 | tmpnam(temp_index); 145 | if((index_file = fopen(temp_index, "w+")) == NULL) 146 | { 147 | fprintf(stderr, "Cannot create index file.\n"); 148 | chdir((const char*)cwd); 149 | return -1; 150 | } 151 | // create tar file 152 | if(tar_fdopen(&tar, fileno(tar_out), NULL, NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) < 0) 153 | { 154 | fprintf(stderr, "Cannot create TAR file.\n"); 155 | chdir((const char*)cwd); 156 | fclose(index_file); 157 | remove(temp_index); 158 | return -1; 159 | } 160 | // sign and add files to tar 161 | if(kindle_sign_and_add_files(dir, "", rsa_pkey, index_file, tar) < 0) 162 | { 163 | fprintf(stderr, "Cannot add files to TAR.\n"); 164 | chdir((const char*)cwd); 165 | fclose(index_file); 166 | tar_close(tar); 167 | remove(temp_index); 168 | return -1; 169 | } 170 | // sign index 171 | rewind(index_file); 172 | tmpnam(temp_index_sig); 173 | if((index_sig_file = fopen(temp_index_sig, "wb")) == NULL || sign_file(index_file, rsa_pkey, index_sig_file) < 0) 174 | { 175 | fprintf(stderr, "Cannot sign index.\n"); 176 | chdir((const char*)cwd); 177 | fclose(index_file); 178 | fclose(index_sig_file); 179 | tar_close(tar); 180 | remove(temp_index); 181 | return -1; 182 | } 183 | // add index to tar 184 | fclose(index_file); 185 | fclose(index_sig_file); 186 | if(tar_append_file(tar, temp_index, INDEX_FILE_NAME) < 0 || tar_append_file(tar, temp_index_sig, INDEX_SIG_FILE_NAME) < 0) 187 | { 188 | fprintf(stderr, "Cannot add index to tar archive.\n"); 189 | chdir((const char*)cwd); 190 | fclose(index_file); 191 | remove(temp_index); 192 | remove(temp_index_sig); 193 | return -1; 194 | } 195 | remove(temp_index); 196 | remove(temp_index_sig); 197 | 198 | // clean up 199 | tar_append_eof(tar); 200 | // we cannot close the tar file yet because it will delete the temp file 201 | remove(INDEX_FILE_NAME); 202 | closedir(dir); 203 | chdir((const char*)cwd); 204 | free(cwd); 205 | return 0; 206 | } 207 | 208 | int kindle_sign_and_add_files(DIR *dir, char *dirname, RSA *rsa_pkey_file, FILE *out_index, TAR *out_tar) 209 | { 210 | char temp_sig[L_tmpnam]; 211 | size_t pathlen; 212 | struct dirent *ent = NULL; 213 | struct stat st; 214 | DIR *next = NULL; 215 | char *absname = NULL; 216 | char *signame = NULL; 217 | FILE *file = NULL; 218 | FILE *sigfile = NULL; 219 | char md5[MD5_HASH_LENGTH+1]; 220 | 221 | tmpnam(temp_sig); 222 | while ((ent = readdir (dir)) != NULL) 223 | { 224 | pathlen = strlen(dirname) + strlen(ent->d_name); 225 | absname = realloc(absname, pathlen + 1); 226 | absname[0] = 0; 227 | strcat(absname, dirname); 228 | strcat(absname, ent->d_name); 229 | absname[pathlen] = 0; 230 | if(stat(ent->d_name, &st) != 0) 231 | { 232 | fprintf(stderr, "Cannot stat %s.\n", absname); 233 | goto on_error; 234 | } 235 | if(S_ISDIR(st.st_mode)) 236 | { 237 | if(strcmp(ent->d_name, "..") == 0 || strcmp(ent->d_name, ".") == 0) 238 | { 239 | continue; 240 | } 241 | absname = realloc(absname, pathlen + 2); 242 | strcat(absname, "/"); 243 | absname[pathlen+1] = 0; 244 | if(chdir(ent->d_name) < 0) 245 | { 246 | fprintf(stderr, "Cannot access input directory.\n"); 247 | goto on_error; 248 | } 249 | if((next = opendir (".")) == NULL) 250 | { 251 | fprintf(stderr, "Cannot access input directory.\n"); 252 | goto on_error; 253 | } 254 | if(kindle_sign_and_add_files(next,absname,rsa_pkey_file,out_index,out_tar) < 0) 255 | { 256 | goto on_error; 257 | } 258 | closedir(next); 259 | } 260 | else 261 | { 262 | // open file 263 | if((file = fopen(ent->d_name, "r")) == NULL) 264 | { 265 | fprintf(stderr, "Cannot open %s for reading!\n", absname); 266 | goto on_error; 267 | } 268 | // calculate md5 hashsum 269 | if(md5_sum(file, md5) != 0) 270 | { 271 | fprintf(stderr, "Cannot calculate hash sum for %s\n", absname); 272 | goto on_error; 273 | } 274 | md5[MD5_HASH_LENGTH] = 0; 275 | rewind(file); 276 | // use openssl to sign file 277 | signame = realloc(signame, strlen(absname) + 5); 278 | signame[0] = 0; 279 | strcat(signame, absname); 280 | strcat(signame, ".sig\0"); 281 | if((sigfile = fopen(temp_sig, "w")) == NULL) // we want a rel path, signame is abs since tar wants abs 282 | { 283 | fprintf(stderr, "Cannot create signature file %s\n", signame); 284 | goto on_error; 285 | } 286 | if(sign_file(file, rsa_pkey_file, sigfile) < 0) 287 | { 288 | fprintf(stderr, "Cannot sign %s\n", absname); 289 | goto on_error; 290 | } 291 | // chmod +x if script 292 | if(IS_SCRIPT(ent->d_name)) 293 | { 294 | if(chmod(ent->d_name, 0777) < 0) 295 | { 296 | fprintf(stderr, "Cannot set executable permission for %s\n", absname); 297 | goto on_error; 298 | } 299 | } 300 | // add file to index 301 | if(fprintf(out_index, "%d %s %s %lld %s\n", (IS_SCRIPT(ent->d_name) ? 129 : 128), md5, absname, st.st_size / BLOCK_SIZE, ent->d_name) < 0) 302 | { 303 | fprintf(stderr, "Cannot write to index file.\n"); 304 | goto on_error; 305 | } 306 | // add file to tar 307 | fclose(file); 308 | if(tar_append_file(out_tar, ent->d_name, absname) < 0) 309 | { 310 | fprintf(stderr, "Cannot add %s to tar archive.\n", absname); 311 | goto on_error; 312 | } 313 | // add sig to tar 314 | fclose(sigfile); 315 | if(tar_append_file(out_tar, temp_sig, signame) < 0) 316 | { 317 | fprintf(stderr, "Cannot add %s to tar archive.\n", signame); 318 | goto on_error; 319 | } 320 | remove(temp_sig); 321 | } 322 | } 323 | chdir(".."); 324 | free(signame); 325 | free(absname); 326 | return 0; 327 | on_error: // Yes, I know GOTOs are bad, but it's more readable than typing what's below for each error above 328 | free(signame); 329 | free(absname); 330 | if(file != NULL) 331 | fclose(file); 332 | if(sigfile != NULL) 333 | fclose(sigfile); 334 | if(next != NULL) 335 | closedir(next); 336 | remove(temp_sig); 337 | return -1; 338 | } 339 | 340 | int kindle_create(UpdateInformation *info, FILE *input_tgz, FILE *output) 341 | { 342 | char buffer[BUFFER_SIZE]; 343 | size_t count; 344 | FILE *temp; 345 | 346 | switch(info->version) 347 | { 348 | case OTAUpdateV2: 349 | if((temp = tmpfile()) == NULL) 350 | { 351 | fprintf(stderr, "Error opening temp file.\n"); 352 | return -1; 353 | } 354 | if(kindle_create_ota_update_v2(info, input_tgz, temp) < 0) // create the update 355 | { 356 | fprintf(stderr, "Error creating update package.\n"); 357 | fclose(temp); 358 | return -1; 359 | } 360 | rewind(temp); // rewind the file before reading back 361 | if(kindle_create_signature(info, temp, output) < 0) // write the signature 362 | { 363 | fprintf(stderr, "Error signing update package.\n"); 364 | fclose(temp); 365 | return -1; 366 | } 367 | rewind(temp); // rewind the file before writing it to output 368 | // write the update 369 | while((count = fread(buffer, sizeof(char), BUFFER_SIZE, temp)) > 0) 370 | { 371 | if(fwrite(buffer, sizeof(char), count, output) < count) 372 | { 373 | fprintf(stderr, "Error writing update to output.\n"); 374 | fclose(temp); 375 | return -1; 376 | } 377 | } 378 | if(ferror(temp) != 0) 379 | { 380 | fprintf(stderr, "Error reading generated update.\n"); 381 | fclose(temp); 382 | return -1; 383 | } 384 | fclose(temp); 385 | return 0; 386 | break; 387 | case OTAUpdate: 388 | return kindle_create_ota_update(info, input_tgz, output); 389 | break; 390 | case RecoveryUpdate: 391 | return kindle_create_recovery(info, input_tgz, output); 392 | break; 393 | case UnknownUpdate: 394 | default: 395 | fprintf(stderr, "Unknown update type.\n"); 396 | break; 397 | } 398 | return -1; 399 | } 400 | 401 | int kindle_create_ota_update_v2(UpdateInformation *info, FILE *input_tgz, FILE *output) 402 | { 403 | int header_size; 404 | unsigned char *header; 405 | int index; 406 | int i; 407 | size_t str_len; 408 | 409 | // first part of the set sized data 410 | header_size = MAGIC_NUMBER_LENGTH + OTA_UPDATE_V2_BLOCK_SIZE; 411 | header = malloc(header_size); 412 | index = 0; 413 | strncpy((char*)header, info->magic_number, MAGIC_NUMBER_LENGTH); 414 | index += MAGIC_NUMBER_LENGTH; 415 | memcpy(&header[index], &info->source_revision, sizeof(uint64_t)); // source 416 | index += sizeof(uint64_t); 417 | memcpy(&header[index], &info->target_revision, sizeof(uint64_t)); // target 418 | index += sizeof(uint64_t); 419 | memcpy(&header[index], &info->num_devices, sizeof(uint16_t)); // device count 420 | index += sizeof(uint16_t); 421 | 422 | // next, we write the devices 423 | header_size += info->num_devices * sizeof(uint16_t); 424 | header = realloc(header, header_size); 425 | for(i = 0; i < info->num_devices; i++) 426 | { 427 | memcpy(&header[index], &info->devices[i], sizeof(uint16_t)); // device 428 | index += sizeof(uint16_t); 429 | } 430 | 431 | // part two of the set sized data 432 | header_size += OTA_UPDATE_V2_PART_2_BLOCK_SIZE; 433 | header = realloc(header, header_size); 434 | memcpy(&header[index], &info->critical, sizeof(uint8_t)); // critical 435 | index += sizeof(uint8_t); 436 | memset(&header[index], 0, sizeof(uint8_t)); // 1 byte padding 437 | index += sizeof(uint8_t); 438 | if(md5_sum(input_tgz, (char*)&header[index]) < 0) // md5 hash 439 | { 440 | fprintf(stderr, "Error calculating MD5 of package.\n"); 441 | free(header); 442 | return -1; 443 | } 444 | rewind(input_tgz); // reset input for later reading 445 | md(&header[index], MD5_HASH_LENGTH); // obfuscate md5 hash 446 | index += MD5_HASH_LENGTH; 447 | memcpy(&header[index], &info->num_meta, sizeof(uint16_t)); // num meta, cannot be casted 448 | index += sizeof(uint16_t); 449 | 450 | // next, we write the meta strings 451 | for(i = 0; i < info->num_meta; i++) 452 | { 453 | str_len = strlen(info->metastrings[i]); 454 | header_size += str_len + sizeof(uint16_t); 455 | header = realloc(header, header_size); 456 | // string length: little endian -> big endian 457 | memcpy(&header[index], &((uint8_t*)&str_len)[1], sizeof(uint8_t)); 458 | index += sizeof(uint8_t); 459 | memcpy(&header[index], &((uint8_t*)&str_len)[0], sizeof(uint8_t)); 460 | index += sizeof(uint8_t); 461 | strncpy((char*)&header[index], info->metastrings[i], str_len); 462 | index += str_len; 463 | } 464 | 465 | // now, we write the header to the file 466 | if(fwrite(header, sizeof(char), header_size, output) < header_size) 467 | { 468 | fprintf(stderr, "Error writing update header.\n"); 469 | free(header); 470 | return -1; 471 | } 472 | 473 | // write the actual update 474 | free(header); 475 | return munger(input_tgz, output, 0); 476 | } 477 | 478 | int kindle_create_signature(UpdateInformation *info, FILE *input_bin, FILE *output) 479 | { 480 | UpdateHeader header; // header to write 481 | 482 | memset(&header, 0, sizeof(UpdateHeader)); // set them to zero 483 | strncpy(header.magic_number, "SP01", 4); // write magic number 484 | header.data.signature.certificate_number = (uint32_t)info->certificate_number; // 4 byte certificate number 485 | if(fwrite(&header, sizeof(char), MAGIC_NUMBER_LENGTH+UPDATE_SIGNATURE_BLOCK_SIZE, output) < MAGIC_NUMBER_LENGTH+UPDATE_SIGNATURE_BLOCK_SIZE) 486 | { 487 | fprintf(stderr, "Error writing update header.\n"); 488 | return -1; 489 | } 490 | // write signature to output 491 | if(sign_file(input_bin, info->sign_pkey, output) < 0) 492 | { 493 | fprintf(stderr, "Error signing update package.\n"); 494 | return -1; 495 | } 496 | return 0; 497 | } 498 | 499 | int kindle_create_ota_update(UpdateInformation *info, FILE *input_tgz, FILE *output) 500 | { 501 | UpdateHeader header; 502 | 503 | memset(&header, 0, sizeof(UpdateHeader)); // set them to zero 504 | strncpy(header.magic_number, info->magic_number, 4); // magic number 505 | header.data.ota_update.source_revision = (uint32_t)info->source_revision; // source 506 | header.data.ota_update.target_revision = (uint32_t)info->target_revision; // target 507 | header.data.ota_update.device = (uint16_t)info->devices[0]; // device 508 | header.data.ota_update.optional = (unsigned char)info->optional; // optional 509 | if(md5_sum(input_tgz, header.data.ota_update.md5_sum) < 0) 510 | { 511 | fprintf(stderr, "Error calculating MD5 of input tgz.\n"); 512 | return -1; 513 | } 514 | rewind(input_tgz); // rewind input 515 | md((unsigned char*)header.data.ota_update.md5_sum, MD5_HASH_LENGTH); // obfuscate md5 hash 516 | 517 | // write header to output 518 | if(fwrite(&header, sizeof(char), MAGIC_NUMBER_LENGTH+OTA_UPDATE_BLOCK_SIZE, output) < MAGIC_NUMBER_LENGTH+OTA_UPDATE_BLOCK_SIZE) 519 | { 520 | fprintf(stderr, "Error writing update header.\n"); 521 | return -1; 522 | } 523 | 524 | // write package to output 525 | return munger(input_tgz, output, 0); 526 | } 527 | 528 | int kindle_create_recovery(UpdateInformation *info, FILE *input_tgz, FILE *output) 529 | { 530 | UpdateHeader header; 531 | 532 | memset(&header, 0, sizeof(UpdateHeader)); // set them to zero 533 | strncpy(header.magic_number, info->magic_number, 4); // magic number 534 | header.data.recovery_update.magic_1 = (uint32_t)info->magic_1; // magic 1 535 | header.data.recovery_update.magic_2 = (uint32_t)info->magic_2; // magic 2 536 | header.data.recovery_update.minor = (uint32_t)info->minor; // minor 537 | header.data.recovery_update.device = (uint32_t)info->devices[0]; // device 538 | if(md5_sum(input_tgz, header.data.recovery_update.md5_sum) < 0) 539 | { 540 | fprintf(stderr, "Error calculating MD5 of input tgz.\n"); 541 | return -1; 542 | } 543 | rewind(input_tgz); // rewind input 544 | md((unsigned char*)header.data.recovery_update.md5_sum, MD5_HASH_LENGTH); // obfuscate md5 hash 545 | 546 | // write header to output 547 | if(fwrite(&header, sizeof(char), MAGIC_NUMBER_LENGTH+RECOVERY_UPDATE_BLOCK_SIZE, output) < MAGIC_NUMBER_LENGTH+RECOVERY_UPDATE_BLOCK_SIZE) 548 | { 549 | fprintf(stderr, "Error writing update header.\n"); 550 | return -1; 551 | } 552 | 553 | // write package to output 554 | return munger(input_tgz, output, 0); 555 | } 556 | 557 | int kindle_create_main(int argc, char *argv[]) 558 | { 559 | int opt; 560 | int opt_index; 561 | static const struct option opts[] = { 562 | { "device", required_argument, NULL, 'd' }, 563 | { "key", required_argument, NULL, 'k' }, 564 | { "bundle", required_argument, NULL, 'b' }, 565 | { "srcrev", required_argument, NULL, 's' }, 566 | { "tgtrev", required_argument, NULL, 't' }, 567 | { "magic1", required_argument, NULL, '1' }, 568 | { "magic2", required_argument, NULL, '2' }, 569 | { "minor", required_argument, NULL, 'm' }, 570 | { "cert", required_argument, NULL, 'c' }, 571 | { "opt", required_argument, NULL, 'o' }, 572 | { "crit", required_argument, NULL, 'r' }, 573 | { "meta", required_argument, NULL, 'x' } 574 | }; 575 | UpdateInformation info = {"\0\0\0\0", UnknownUpdate, get_default_key(), 0, UINT32_MAX, 0, 0, 0, 0, NULL, CertificateDeveloper, 0, 0, 0, NULL }; 576 | struct stat st_buf; 577 | FILE *input; 578 | FILE *temp; 579 | FILE *output; 580 | BIO *bio; 581 | int i; 582 | 583 | // defaults 584 | output = stdout; 585 | input = NULL; 586 | temp = NULL; 587 | // update type 588 | if(argc < 2) 589 | { 590 | fprintf(stderr, "Not enough arguments.\n"); 591 | return -1; 592 | } 593 | if(strncmp(argv[0], "ota2", 4) == 0) 594 | { 595 | info.version = OTAUpdateV2; 596 | } 597 | else if(strncmp(argv[0], "ota", 3) == 0) 598 | { 599 | info.version = OTAUpdate; 600 | strncpy(info.magic_number, "FC02", 4); 601 | } 602 | else if(strncmp(argv[0], "recovery", 8) == 0) 603 | { 604 | info.version = RecoveryUpdate; 605 | strncpy(info.magic_number, "FB02", 4); 606 | } 607 | else 608 | { 609 | fprintf(stderr, "Invalid update type.\n"); 610 | return -1; 611 | } 612 | argc--; argv++; // next argument 613 | // arguments 614 | optind = -1; // hack to get around the fact that we skipped some arguments 615 | while((opt = getopt_long(argc, argv, "d:k:b:s:t:1:2:m:c:o:r:x:", opts, &opt_index)) != -1) 616 | { 617 | switch(opt) 618 | { 619 | case 'd': 620 | info.devices = realloc(info.devices, ++info.num_devices * sizeof(Device)); 621 | if(strncmp(optarg, "k1", 2) == 0) 622 | info.devices[info.num_devices-1] = Kindle1; 623 | else if(strncmp(optarg, "k2", 2) == 0) 624 | info.devices[info.num_devices-1] = Kindle2US; 625 | else if(strncmp(optarg, "k2i", 3) == 0) 626 | info.devices[info.num_devices-1] = Kindle2International; 627 | else if(strncmp(optarg, "dx", 2) == 0) 628 | info.devices[info.num_devices-1] = KindleDXUS; 629 | else if(strncmp(optarg, "dxi", 3) == 0) 630 | info.devices[info.num_devices-1] = KindleDXInternational; 631 | else if(strncmp(optarg, "dxg", 3) == 0) 632 | info.devices[info.num_devices-1] = KindleDXGraphite; 633 | else if(strncmp(optarg, "k3w", 3) == 0) 634 | info.devices[info.num_devices-1] = Kindle3Wifi; 635 | else if(strncmp(optarg, "k3g", 2) == 0) 636 | info.devices[info.num_devices-1] = Kindle3Wifi3G; 637 | else if(strncmp(optarg, "k3gb", 3) == 0) 638 | info.devices[info.num_devices-1] = Kindle3Wifi3GEurope; 639 | else if(strncmp(optarg, "k4", 2) == 0) 640 | { 641 | info.devices[info.num_devices-1] = Kindle4NonTouch; 642 | strncpy(info.magic_number, "FC04", 4); 643 | } 644 | else if(strncmp(optarg, "k5w", 3) == 0) 645 | { 646 | info.devices[info.num_devices-1] = Kindle5TouchWifi; 647 | strncpy(info.magic_number, "FD04", 4); 648 | } 649 | else if(strncmp(optarg, "k5g", 2) == 0) 650 | { 651 | info.devices[info.num_devices-1] = Kindle5TouchWifi3G; 652 | strncpy(info.magic_number, "FD04", 4); 653 | } 654 | else 655 | { 656 | fprintf(stderr, "Unknown device %s.\n", optarg); 657 | goto do_error; 658 | } 659 | break; 660 | case 'k': 661 | if((bio = BIO_new_file(optarg, "rb")) == NULL || PEM_read_bio_RSAPrivateKey(bio, &info.sign_pkey, NULL, NULL) == NULL) 662 | { 663 | fprintf(stderr, "Key %s cannot be loaded.\n", optarg); 664 | goto do_error; 665 | } 666 | break; 667 | case 'b': 668 | strncpy(info.magic_number, optarg, 4); 669 | if((info.version = get_bundle_version(optarg)) == UnknownUpdate) 670 | { 671 | fprintf(stderr, "Invalid bundle version %s.\n", optarg); 672 | goto do_error; 673 | } 674 | break; 675 | case 's': 676 | info.source_revision = strtoul(optarg, NULL, 0); 677 | break; 678 | case 't': 679 | info.target_revision = strtoul(optarg, NULL, 0); 680 | break; 681 | case '1': 682 | info.magic_1 = atoi(optarg); 683 | break; 684 | case '2': 685 | info.magic_2 = atoi(optarg); 686 | break; 687 | case 'm': 688 | info.minor = atoi(optarg); 689 | break; 690 | case 'c': 691 | info.certificate_number = (CertificateNumber)atoi(optarg); 692 | break; 693 | case 'o': 694 | info.optional = (uint16_t)atoi(optarg); 695 | break; 696 | case 'r': 697 | info.critical = (uint16_t)atoi(optarg); 698 | break; 699 | case 'x': 700 | if(strchr(optarg, '=') == NULL) // metastring must contain = 701 | { 702 | fprintf(stderr, "Invalid metastring. Format: key=value, input: %s\n", optarg); 703 | goto do_error; 704 | } 705 | if(strlen(optarg) > 0xFFFF) 706 | { 707 | fprintf(stderr, "Metastring too long. Max length: %d, input: %s\n", 0xFFFF, optarg); 708 | goto do_error; 709 | } 710 | info.metastrings = realloc(info.metastrings, ++info.num_meta * sizeof(char*)); 711 | info.metastrings[info.num_meta-1] = strdup(optarg); 712 | break; 713 | } 714 | } 715 | // validation 716 | if(info.num_devices < 1 || (info.version != OTAUpdateV2 && info.num_devices > 1)) 717 | { 718 | fprintf(stderr, "Invalid number of supported devices, %d, for this update type.\n", info.num_devices); 719 | goto do_error; 720 | } 721 | if(info.version != OTAUpdateV2 && (info.source_revision > UINT32_MAX || info.target_revision > UINT32_MAX)) 722 | { 723 | fprintf(stderr, "Source/target revision for this update type cannot exceed %u\n", UINT32_MAX); 724 | goto do_error; 725 | } 726 | argc -= (optind-1); argv += optind; // next argument 727 | // input 728 | if(argc < 1) 729 | { 730 | fprintf(stderr, "No input found.\n"); 731 | goto do_error; 732 | } 733 | if(stat(argv[0], &st_buf) != 0) 734 | { 735 | fprintf(stderr, "Cannot read input.\n"); 736 | goto do_error; 737 | } 738 | if(S_ISDIR (st_buf.st_mode)) 739 | { 740 | // input is a directory 741 | if((temp = tmpfile()) == NULL || kindle_create_tar_from_directory(argv[0], temp, info.sign_pkey) < 0) 742 | { 743 | fprintf(stderr, "Cannot create archive.\n"); 744 | goto do_error; 745 | } 746 | rewind(temp); 747 | 748 | if((input = gzip_file(temp)) == NULL) 749 | { 750 | fprintf(stderr, "Cannot compress archive.\n"); 751 | goto do_error; 752 | } 753 | fclose(temp); 754 | } 755 | else 756 | { 757 | // input is a file 758 | if((input = fopen(argv[0], "rb")) == NULL) 759 | { 760 | fprintf(stderr, "Cannot read input.\n"); 761 | goto do_error; 762 | } 763 | } 764 | argc--; argv++; // next argument 765 | // output 766 | if(argc > 0) 767 | { 768 | if((output = fopen(argv[0], "wb")) == NULL) 769 | { 770 | fprintf(stderr, "Cannot create output.\n"); 771 | goto do_error; 772 | } 773 | } 774 | // write it all to the output 775 | if(kindle_create(&info, input, output) < 0) 776 | { 777 | fprintf(stderr, "Cannot write update to output.\n"); 778 | goto do_error; 779 | } 780 | fclose(input); 781 | return 0; 782 | do_error: 783 | free(info.devices); 784 | for(i = 0; i < info.num_meta; i++) 785 | free(info.metastrings[i]); 786 | free(info.metastrings); 787 | fclose(temp); 788 | fclose(input); 789 | return -1; 790 | } 791 | -------------------------------------------------------------------------------- /KindleTool/kindle_tool.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // KindleTool 4 | // 5 | // Copyright (C) 2011 Yifan Lu 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #include "kindle_tool.h" 22 | 23 | void md(unsigned char *bytes, size_t length) 24 | { 25 | int i; 26 | for(i = 0; i < length; i++) 27 | { 28 | bytes[i] = ( ( bytes[i] >> 4 | bytes[i] << 4 ) & 0xFF ) ^ 0x7A; 29 | } 30 | } 31 | 32 | void dm(unsigned char *bytes, size_t length) 33 | { 34 | int i; 35 | for(i = 0; i < length; i++) 36 | { 37 | bytes[i] = ( bytes[i] ^ 0x7A ); 38 | bytes[i] = ( bytes[i] >> 4 | bytes[i] << 4 ) & 0xFF; 39 | } 40 | } 41 | 42 | int munger(FILE *input, FILE *output, size_t length) 43 | { 44 | unsigned char bytes[BUFFER_SIZE]; 45 | size_t bytes_read; 46 | size_t bytes_written; 47 | 48 | while((bytes_read = fread(bytes, sizeof(char), (length < BUFFER_SIZE && length > 0 ? length : BUFFER_SIZE), input)) > 0) 49 | { 50 | md(bytes, bytes_read); 51 | bytes_written = fwrite(bytes, sizeof(char), bytes_read, output); 52 | if(ferror(output) != 0) 53 | { 54 | fprintf(stderr, "Error munging, cannot write to output.\n"); 55 | return -1; 56 | } 57 | else if(bytes_written < bytes_read) 58 | { 59 | fprintf(stderr, "Error munging, read %zu bytes but only wrote %zu bytes\n", bytes_read, bytes_written); 60 | return -1; 61 | } 62 | length -= bytes_read; 63 | } 64 | if(ferror(input) != 0) 65 | { 66 | fprintf(stderr, "Error munging, cannot read input.\n"); 67 | return -1; 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | int demunger(FILE *input, FILE *output, size_t length) 74 | { 75 | unsigned char bytes[BUFFER_SIZE]; 76 | size_t bytes_read; 77 | size_t bytes_written; 78 | while((bytes_read = fread(bytes, sizeof(char), (length < BUFFER_SIZE && length > 0 ? length : BUFFER_SIZE), input)) > 0) 79 | { 80 | dm(bytes, bytes_read); 81 | bytes_written = fwrite(bytes, sizeof(char), bytes_read, output); 82 | if(ferror(output) != 0) 83 | { 84 | fprintf(stderr, "Error munging, cannot write to output.\n"); 85 | return -1; 86 | } 87 | else if(bytes_written < bytes_read) 88 | { 89 | fprintf(stderr, "Error munging, read %zu bytes but only wrote %zu bytes\n", bytes_read, bytes_written); 90 | return -1; 91 | } 92 | length -= bytes_read; 93 | } 94 | if(ferror(input) != 0) 95 | { 96 | fprintf(stderr, "Error munging, cannot read input.\n"); 97 | return -1; 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | const char *convert_device_id(Device dev) 104 | { 105 | switch(dev) 106 | { 107 | case Kindle1: 108 | return "Kindle 1"; 109 | case Kindle2US: 110 | return "Kindle 2 US"; 111 | case Kindle2International: 112 | return "Kindle 2 International"; 113 | case KindleDXUS: 114 | return "Kindle DX US"; 115 | case KindleDXInternational: 116 | return "Kindle DX International"; 117 | case KindleDXGraphite: 118 | return "Kindle DX Graphite"; 119 | case Kindle3Wifi: 120 | return "Kindle 3 Wifi"; 121 | case Kindle3Wifi3G: 122 | return "Kindle 3 Wifi+3G"; 123 | case Kindle3Wifi3GEurope: 124 | return "Kindle 3 Wifi+3G Europe"; 125 | case Kindle4NonTouch: 126 | return "Kindle 4 Non-Touch"; 127 | case Kindle5TouchWifi: 128 | return "Kindle 5 Touch Wifi"; 129 | case Kindle5TouchWifi3G: 130 | return "Kindle 5 Touch Wifi+3G"; 131 | case KindleUnknown: 132 | default: 133 | return "Unknown"; 134 | } 135 | } 136 | 137 | BundleVersion get_bundle_version(char magic_number[4]) 138 | { 139 | if(!strncmp(magic_number, "FB02", 4) || !strncmp(magic_number, "FB01", 4)) 140 | return RecoveryUpdate; 141 | else if(!strncmp(magic_number, "FC02", 4) || !strncmp(magic_number, "FD03", 4)) 142 | return OTAUpdate; 143 | else if(!strncmp(magic_number, "FC04", 4) || !strncmp(magic_number, "FD04", 4) || !strncmp(magic_number, "FL01", 4)) 144 | return OTAUpdateV2; 145 | else if(!strncmp(magic_number, "SP01", 4)) 146 | return UpdateSignature; 147 | else 148 | return UnknownUpdate; 149 | } 150 | 151 | int md5_sum(FILE *input, char output_string[MD5_HASH_LENGTH]) 152 | { 153 | unsigned char bytes[BUFFER_SIZE]; 154 | size_t bytes_read; 155 | MD5_CTX md5; 156 | unsigned char output[MD5_DIGEST_LENGTH]; 157 | char output_string_temp[MD5_HASH_LENGTH+1]; // sprintf adds trailing null, we do not want that! 158 | int i; 159 | 160 | MD5_Init(&md5); 161 | while((bytes_read = fread(bytes, sizeof(char), BUFFER_SIZE, input)) > 0) 162 | { 163 | MD5_Update(&md5, bytes, bytes_read); 164 | } 165 | if(ferror(input) != 0) 166 | { 167 | fprintf(stderr, "Error reading input.\n"); 168 | return -1; 169 | } 170 | MD5_Final(output, &md5); 171 | for(i = 0; i < MD5_DIGEST_LENGTH; i++) 172 | { 173 | sprintf(output_string_temp+(i*2), "%02x", output[i]); 174 | } 175 | memcpy(output_string, output_string_temp, MD5_HASH_LENGTH); // remove the trailing null. any better way to do this? 176 | return 0; 177 | } 178 | 179 | RSA *get_default_key() 180 | { 181 | static RSA *rsa_pkey = NULL; 182 | BIO *bio; 183 | if(rsa_pkey == NULL) 184 | { 185 | bio = BIO_new_mem_buf((void*)SIGN_KEY, -1); 186 | if(PEM_read_bio_RSAPrivateKey(bio, &rsa_pkey, NULL, NULL) == NULL) 187 | { 188 | fprintf(stderr, "Error loading RSA Private Key File\n"); 189 | return NULL; 190 | } 191 | } 192 | return rsa_pkey; 193 | } 194 | 195 | int kindle_print_help(const char *prog_name) 196 | { 197 | printf( 198 | "usage:\n" 199 | " %s dm [ ] [ ]\n" 200 | " Obfuscates data using Amazon's update algorithm.\n" 201 | " If no input is provided, input from stdin\n" 202 | " If no output is provided, output to stdout\n" 203 | " \n" 204 | " %s md [ ] [ ]\n" 205 | " Deobfuscates data using Amazon's update algorithm.\n" 206 | " If no input is provided, input from stdin\n" 207 | " If no output is provided, output to stdout\n" 208 | " \n" 209 | " %s convert [options] \n" 210 | " Converts a Kindle update package to a gzipped TAR file, and delete input\n" 211 | " \n" 212 | " Options:\n" 213 | " -c, --stdout Write to standard output, keeping original files unchanged\n" 214 | " -i, --info Just print the package information, no conversion done\n" 215 | " -s, --sig OTA V2 updates only. Extract the package signature to file.\n" 216 | " \n" 217 | " %s extract \n" 218 | " Extracts a Kindle update package to a directory\n" 219 | " \n" 220 | " %s create [options] [ ]\n" 221 | " Creates a Kindle update package\n" 222 | " If input is a directory, all files in it will be packed into an update\n" 223 | " If input is a GZIP file, it will be converted to an update.\n" 224 | " If no output is provided, output to stdout.\n" 225 | " In case of OTA updates, all files with the extension \".ffs\" and will be treated as update scripts\n" 226 | " \n" 227 | " Type:\n" 228 | " ota OTA V1 update package. Works on Kindle 3.0 and below.\n" 229 | " ota2 OTA V2 signed update package. Works on Kindle 4.0 and up.\n" 230 | " recovery Recovery package for restoring partitions.\n" 231 | " \n" 232 | " Devices:\n" 233 | " OTA V1 packages only support one device. OTA V2 packages can support multiple devices.\n" 234 | " \n" 235 | " -d, --device k1 Kindle 1\n" 236 | " -d, --device k2 Kindle 2 US\n" 237 | " -d, --device k2i Kindle 2 International\n" 238 | " -d, --device dx Kindle DX US\n" 239 | " -d, --device dxi Kindle DX International\n" 240 | " -d, --device dxg Kindle DX Graphite\n" 241 | " -d, --device k3w Kindle 3 Wifi\n" 242 | " -d, --device k3g Kindle 3 Wifi+3G\n" 243 | " -d, --device k3gb Kindle 3 Wifi+3G Europe\n" 244 | " -d, --device k4 Kindle 4 (No Touch)\n" 245 | " -d, --device k5w Kindle 5 (Kindle Touch) Wifi\n" 246 | " -d, --device k5g Kindle 5 (Kindle Touch) Wifi+3G\n" 247 | " \n" 248 | " Options:\n" 249 | " All the following options are optional and advanced.\n" 250 | " -k, --key PEM file containing RSA private key to sign update. Default is popular jailbreak key.\n" 251 | " -b, --bundle Manually specify package magic number. Overrides \"type\". Valid bundle versions:\n" 252 | " FB01, FB02 = recovery; FC02, FD03 = ota; FC04, FD04, FL01 = ota2\n" 253 | " -s, --srcrev OTA updates only. Source revision. OTA V1 uses uint, OTA V2 uses ulong.\n" 254 | " Lowest version of device that package supports. Default is 0.\n" 255 | " -t, --tgtrev OTA updates only. Target revision. OTA V1 uses uint, OTA V2 uses ulong.\n" 256 | " Highest version of device that package supports. Default is max int value.\n" 257 | " -1, --magic1 Recovery updates only. Magic number 1. Default is 0.\n" 258 | " -2, --magic2 Recovery updates only. Magic number 2. Default is 0.\n" 259 | " -m, --minor Recovery updates only. Minor number. Default is 0.\n" 260 | " -c, --cert OTA V2 updates only. The number of the certificate to use (found in /etc/uks on device). Default is 0.\n" 261 | " 0 = pubdevkey01.pem, 1 = pubprodkey01.pem, 2 = pubprodkey02.pem\n" 262 | " -o, --opt OTA V1 updates only. One byte optional data expressed as a number. Default is 0.\n" 263 | " -r, --crit OTA V2 updates only. One byte optional data expressed as a number. Default is 0.\n" 264 | " -x, --meta OTA V2 updates only. An optional string to add. Multiple \"--meta\" options supported.\n" 265 | " Format of metastring must be: key=value\n" 266 | " \n" 267 | " %s info \n" 268 | " Get the default root password\n" 269 | " \n" 270 | "notices:\n" 271 | " 1) Kindle 4.0+ has a known bug that prevents some updates with meta-strings to run.\n" 272 | " 2) Currently, even though OTA V2 supports updates that run on multiple devices, it is not possible to create a update package that will run on both the Kindle 4 (No Touch) and Kindle 5 (Kindle Touch).\n" 273 | , prog_name, prog_name, prog_name, prog_name, prog_name, prog_name); 274 | return 0; 275 | } 276 | 277 | int kindle_obfuscate_main(int argc, char *argv[]) 278 | { 279 | FILE *input; 280 | FILE *output; 281 | input = stdin; 282 | output = stdout; 283 | if(argc > 1) 284 | { 285 | if((output = fopen(argv[1], "wb")) == NULL) 286 | { 287 | fprintf(stderr, "Cannot open output for writing.\n"); 288 | return -1; 289 | } 290 | } 291 | if(argc > 0) 292 | { 293 | if((input = fopen(argv[0], "rb")) == NULL) 294 | { 295 | fprintf(stderr, "Cannot open input for reading.\n"); 296 | fclose(output); 297 | return -1; 298 | } 299 | } 300 | if(demunger(input, output, 0) < 0) 301 | { 302 | fprintf(stderr, "Cannot obfuscate.\n"); 303 | fclose(input); 304 | fclose(output); 305 | return -1; 306 | } 307 | fclose(input); 308 | fclose(output); 309 | return 0; 310 | } 311 | 312 | int kindle_deobfuscate_main(int argc, char *argv[]) 313 | { 314 | FILE *input; 315 | FILE *output; 316 | input = stdin; 317 | output = stdout; 318 | if(argc > 1) 319 | { 320 | if((output = fopen(argv[1], "wb")) == NULL) 321 | { 322 | fprintf(stderr, "Cannot open output for writing.\n"); 323 | return -1; 324 | } 325 | } 326 | if(argc > 0) 327 | { 328 | if((input = fopen(argv[0], "rb")) == NULL) 329 | { 330 | fprintf(stderr, "Cannot open input for reading.\n"); 331 | fclose(output); 332 | return -1; 333 | } 334 | } 335 | if(munger(input, output, 0) < 0) 336 | { 337 | fprintf(stderr, "Cannot deobfuscate.\n"); 338 | fclose(input); 339 | fclose(output); 340 | return -1; 341 | } 342 | fclose(input); 343 | fclose(output); 344 | return 0; 345 | } 346 | 347 | int kindle_info_main(int argc, char *argv[]) 348 | { 349 | char *serial_no; 350 | char md5[MD5_HASH_LENGTH]; 351 | FILE *temp; 352 | int i; 353 | if(argc < 1) 354 | { 355 | fprintf(stderr, "No serial number found in input.\n"); 356 | return -1; 357 | } 358 | serial_no = argv[0]; 359 | temp = tmpfile(); 360 | if(strlen(serial_no) != SERIAL_NO_LENGTH) 361 | { 362 | fprintf(stderr, "Serial number must be 16 digits long (no spaces). Example: %s\n", "B00XXXXXXXXXXXXX"); 363 | return -1; 364 | } 365 | for(i = 0; i < SERIAL_NO_LENGTH; i++) 366 | { 367 | if(islower(serial_no[i])) 368 | { 369 | serial_no[i] = toupper(serial_no[i]); 370 | } 371 | } 372 | // find root password 373 | if(fprintf(temp, "%s\n", serial_no) < SERIAL_NO_LENGTH) 374 | { 375 | fprintf(stderr, "Cannot write serial to temporary file.\n"); 376 | fclose(temp); 377 | return -1; 378 | } 379 | rewind(temp); 380 | if(md5_sum(temp, md5) < 0) 381 | { 382 | fprintf(stderr, "Cannot calculate MD5 of serial number.\n"); 383 | fclose(temp); 384 | return -1; 385 | } 386 | fprintf(stderr, "Root PW %s%.*s\n", "fiona", 4, &md5[7]); 387 | fclose(temp); 388 | return 0; 389 | } 390 | 391 | int main (int argc, char *argv[]) 392 | { 393 | char *prog_name; 394 | prog_name = argv[0]; 395 | argc--; argv++; // discard program name for easier parsing 396 | if(freopen(NULL, "rb", stdin) == NULL) 397 | { 398 | fprintf(stderr, "Cannot set stdin to binary mode.\n"); 399 | return -1; 400 | } 401 | if(freopen(NULL, "wb", stdout) == NULL) 402 | { 403 | fprintf(stderr, "Cannot set stdout to binary mode.\n"); 404 | return -1; 405 | } 406 | if(argc < 1 || strncmp(argv[0], "help", 4) == 0) 407 | return kindle_print_help(prog_name); 408 | if(strncmp(argv[0], "dm", 2) == 0) 409 | return kindle_obfuscate_main(--argc, ++argv); 410 | if(strncmp(argv[0], "md", 2) == 0) 411 | return kindle_deobfuscate_main(--argc,++argv); 412 | if(strncmp(argv[0], "convert", 7) == 0) 413 | return kindle_convert_main(--argc, ++argv); 414 | if(strncmp(argv[0], "extract", 7) == 0) 415 | return kindle_extract_main(--argc, ++argv); 416 | if(strncmp(argv[0], "create", 6) == 0) 417 | return kindle_create_main(--argc, ++argv); 418 | if(strncmp(argv[0], "info", 4) == 0) 419 | return kindle_info_main(--argc, ++argv); 420 | } 421 | -------------------------------------------------------------------------------- /KindleTool/kindle_tool.h: -------------------------------------------------------------------------------- 1 | // 2 | // kindle_tool.h 3 | // KindleTool 4 | // 5 | // Copyright (C) 2011 Yifan Lu 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #ifndef KINDLETOOL 22 | #define KINDLETOOL 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #define BUFFER_SIZE 1024 43 | #define BLOCK_SIZE 64 44 | 45 | #define MAGIC_NUMBER_LENGTH 4 46 | #define MD5_HASH_LENGTH 32 47 | 48 | #define OTA_UPDATE_BLOCK_SIZE 60 49 | #define OTA_UPDATE_V2_BLOCK_SIZE 18 50 | #define OTA_UPDATE_V2_PART_2_BLOCK_SIZE 36 51 | #define RECOVERY_UPDATE_BLOCK_SIZE 131068 52 | #define UPDATE_SIGNATURE_BLOCK_SIZE 60 53 | 54 | #define CERTIFICATE_DEV_SIZE 128 55 | #define CERTIFICATE_1K_SIZE 128 56 | #define CERTIFICATE_2K_SIZE 256 57 | 58 | #define INDEX_FILE_NAME "update-filelist.dat" 59 | #define INDEX_SIG_FILE_NAME "update-filelist.dat.sig" 60 | 61 | #define SERIAL_NO_LENGTH 16 62 | 63 | #define IS_SCRIPT(filename) (strncmp(filename+(strlen(filename)-4), ".ffs", 4) == 0) 64 | 65 | typedef enum { 66 | UpdateSignature, 67 | OTAUpdateV2, 68 | OTAUpdate, 69 | RecoveryUpdate, 70 | UnknownUpdate = -1 71 | } BundleVersion; 72 | 73 | typedef enum { 74 | CertificateDeveloper = 0x00, 75 | Certificate1K = 0x01, 76 | Certificate2K = 0x02, 77 | CertificateUnknown = 0xFF 78 | } CertificateNumber; 79 | 80 | typedef enum { 81 | Kindle1 = 0x01, 82 | Kindle2US = 0x02, 83 | Kindle2International = 0x03, 84 | KindleDXUS = 0x04, 85 | KindleDXInternational = 0x05, 86 | KindleDXGraphite = 0x09, 87 | Kindle3Wifi = 0x08, 88 | Kindle3Wifi3G = 0x06, 89 | Kindle3Wifi3GEurope = 0x0A, 90 | Kindle4NonTouch = 0x0E, 91 | Kindle5TouchWifi3G = 0x0F, 92 | Kindle5TouchWifi = 0x11, 93 | KindleUnknown = 0x00 94 | } Device; 95 | 96 | typedef struct { 97 | CertificateNumber certificate_number; 98 | } UpdateSignatureHeader; 99 | 100 | typedef struct { 101 | uint32_t source_revision; 102 | uint32_t target_revision; 103 | uint16_t device; 104 | unsigned char optional; 105 | unsigned char unused; 106 | char md5_sum[MD5_HASH_LENGTH]; 107 | } OTAUpdateHeader; 108 | 109 | typedef struct { 110 | unsigned char unused[12]; 111 | char md5_sum[MD5_HASH_LENGTH]; 112 | uint32_t magic_1; 113 | uint32_t magic_2; 114 | uint32_t minor; 115 | uint32_t device; 116 | } RecoveryUpdateHeader; 117 | 118 | typedef struct { 119 | char magic_number[MAGIC_NUMBER_LENGTH]; 120 | union { 121 | OTAUpdateHeader ota_update; 122 | RecoveryUpdateHeader recovery_update; 123 | UpdateSignatureHeader signature; 124 | unsigned char ota_header_data[OTA_UPDATE_BLOCK_SIZE]; 125 | unsigned char signature_header_data[UPDATE_SIGNATURE_BLOCK_SIZE]; 126 | unsigned char recovery_header_data[RECOVERY_UPDATE_BLOCK_SIZE]; 127 | } data; 128 | } UpdateHeader; 129 | 130 | typedef struct { 131 | char magic_number[MAGIC_NUMBER_LENGTH]; 132 | BundleVersion version; 133 | RSA *sign_pkey; 134 | uint64_t source_revision; 135 | uint64_t target_revision; 136 | uint32_t magic_1; 137 | uint32_t magic_2; 138 | uint32_t minor; 139 | int num_devices; 140 | Device *devices; 141 | CertificateNumber certificate_number; 142 | unsigned char optional; 143 | unsigned char critical; 144 | int num_meta; 145 | char **metastrings; 146 | } UpdateInformation; 147 | 148 | static const char SIGN_KEY[] = 149 | "-----BEGIN RSA PRIVATE KEY-----\n" 150 | "MIICXgIBAAKBgQDJn1jWU+xxVv/eRKfCPR9e47lPWN2rH33z9QbfnqmCxBRLP6mM\n" 151 | "jGy6APyycQXg3nPi5fcb75alZo+Oh012HpMe9LnpeEgloIdm1E4LOsyrz4kttQtG\n" 152 | "RlzCErmBGt6+cAVEV86y2phOJ3mLk0Ek9UQXbIUfrvyJnS2MKLG2cczjlQIDAQAB\n" 153 | "AoGASLym1POD2kOznSERkF5yoc3vvXNmzORYkRk1eJkJuDY6yAbYiO7kDppqj4l8\n" 154 | "wGogTpv98OMXauY8JgQj6tgO5LkY2upttukDr8uhE2z9Dh7HMZV/rDYa+9rybJus\n" 155 | "RiAQDmF+VCzY2HirjpsSzgRu0r82NC8znNm2eGORys9BvmECQQDoIokOr0fYz3UT\n" 156 | "SbHfD3engXFPZ+JaJqU8xayR7C+Gp5I0CgSnCDTQVgdkVGbPuLVYiWDIcEaxjvVr\n" 157 | "hXYt2Ac9AkEA3lnERgg0RmWBC3K8toCyfDvr8eXao+xgUJ3lNWbqS0HtwxczwnIE\n" 158 | "H49IIDojbTnLUr3OitFMZuaJuT2MtWzTOQJBAK6GCHU54tJmZqbxqQEDJ/qPnxkM\n" 159 | "CWmt1F00YOH0qGacZZcqUQUjblGT3EraCdHyFKVT46fOgdfMm0cTOB6PZCECQQDI\n" 160 | "s5Zq8HTfJjg5MTQOOFTjtuLe0m9sj6zQl/WRInhRvgzzkDn0Rh5armaYUGIx8X0K\n" 161 | "DrIks4+XQnkGb/xWtwhhAkEA3FdnrsFiCNNJhvit2aTmtLzXxU46K+sV6NIY1tEJ\n" 162 | "G+RFzLRwO4IFDY4a/dooh1Yh1iFFGjcmpqza6tRutaw8zA==\n" 163 | "-----END RSA PRIVATE KEY-----\0"; 164 | 165 | void md(unsigned char *, size_t); 166 | void dm(unsigned char *, size_t); 167 | int munger(FILE *, FILE *, size_t); 168 | int demunger(FILE *, FILE *, size_t); 169 | const char *convert_device_id(Device); 170 | BundleVersion get_bundle_version(char*); 171 | int md5_sum(FILE *, char*); 172 | RSA *get_default_key(); 173 | int kindle_print_help(const char *prog_name); 174 | int kindle_deobfuscate_main(int, char **); 175 | int kindle_obfuscate_main(int, char **); 176 | int kindle_info_main(int, char **); 177 | 178 | FILE *gunzip_file(FILE *); 179 | int kindle_read_bundle_header(UpdateHeader *, FILE *); 180 | int kindle_convert(FILE *, FILE *, FILE *); 181 | int kindle_convert_ota_update_v2(FILE *, FILE *); 182 | int kindle_convert_signature(UpdateHeader *, FILE *, FILE *); 183 | int kindle_convert_ota_update(UpdateHeader *, FILE *, FILE *); 184 | int kindle_convert_recovery(UpdateHeader *, FILE *, FILE *); 185 | int kindle_convert_main(int, char **); 186 | int kindle_extract_main(int, char **); 187 | 188 | int sign_file(FILE *, RSA *, FILE *); 189 | FILE *gzip_file(FILE *); 190 | int kindle_create_tar_from_directory(const char *, FILE *, RSA *); 191 | int kindle_sign_and_add_files(DIR *, char *, RSA *, FILE *, TAR *); 192 | int kindle_create(UpdateInformation *, FILE *, FILE *); 193 | int kindle_create_ota_update_v2(UpdateInformation *, FILE *, FILE *); 194 | int kindle_create_signature(UpdateInformation *, FILE *, FILE *); 195 | int kindle_create_ota_update(UpdateInformation *, FILE *, FILE *); 196 | int kindle_create_recovery(UpdateInformation *, FILE *, FILE *); 197 | int kindle_create_main(int, char **); 198 | 199 | #endif 200 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cd KindleTool && make 3 | 4 | clean: 5 | cd KindleTool && make clean 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | usage: 2 | KindleTool dm [ ] [ ] 3 | Obfuscates data using Amazon's update algorithm. 4 | If no input is provided, input from stdin 5 | If no output is provided, output to stdout 6 | 7 | KindleTool md [ ] [ ] 8 | Deobfuscates data using Amazon's update algorithm. 9 | If no input is provided, input from stdin 10 | If no output is provided, output to stdout 11 | 12 | KindleTool convert [options] 13 | Converts a Kindle update package to a gzipped TAR file, and delete input 14 | 15 | Options: 16 | -c, --stdout Write to standard output, keeping original files unchanged 17 | -i, --info Just print the package information, no conversion done 18 | -s, --sig OTA V2 updates only. Extract the package signature to file. 19 | 20 | KindleTool extract 21 | Extracts a Kindle update package to a directory 22 | 23 | KindleTool create [options] [ ] 24 | Creates a Kindle update package 25 | If input is a directory, all files in it will be packed into an update 26 | If input is a GZIP file, it will be converted to an update. 27 | If no output is provided, output to stdout. 28 | In case of OTA updates, all files with the extension ".ffs" and will be treated as update scripts 29 | 30 | Type: 31 | ota OTA V1 update package. Works on Kindle 3.0 and below. 32 | ota2 OTA V2 signed update package. Works on Kindle 4.0 and up. 33 | recovery Recovery package for restoring partitions. 34 | 35 | Devices: 36 | OTA V1 packages only support one device. OTA V2 packages can support multiple devices. 37 | 38 | -d, --device k1 Kindle 1 39 | -d, --device k2 Kindle 2 US 40 | -d, --device k2i Kindle 2 International 41 | -d, --device dx Kindle DX US 42 | -d, --device dxi Kindle DX International 43 | -d, --device dxg Kindle DX Graphite 44 | -d, --device k3w Kindle 3 Wifi 45 | -d, --device k3g Kindle 3 Wifi+3G 46 | -d, --device k3gb Kindle 3 Wifi+3G Europe 47 | -d, --device k4 Kindle 4 (No Touch) 48 | -d, --device k5w Kindle 5 (Kindle Touch) Wifi 49 | -d, --device k5g Kindle 5 (Kindle Touch) Wifi+3G 50 | 51 | Options: 52 | All the following options are optional and advanced. 53 | -k, --key PEM file containing RSA private key to sign update. Default is popular jailbreak key. 54 | -b, --bundle Manually specify package magic number. Overrides "type". Valid bundle versions: 55 | FB01, FB02 = recovery; FC02, FD03 = ota; FC04, FD04, FL01 = ota2 56 | -s, --srcrev OTA updates only. Source revision. OTA V1 uses uint, OTA V2 uses ulong. 57 | Lowest version of device that package supports. Default is 0. 58 | -t, --tgtrev OTA updates only. Target revision. OTA V1 uses uint, OTA V2 uses ulong. 59 | Highest version of device that package supports. Default is max int value. 60 | -1, --magic1 Recovery updates only. Magic number 1. Default is 0. 61 | -2, --magic2 Recovery updates only. Magic number 2. Default is 0. 62 | -m, --minor Recovery updates only. Minor number. Default is 0. 63 | -c, --cert OTA V2 updates only. The number of the certificate to use (found in /etc/uks on device). Default is 0. 64 | 0 = pubdevkey01.pem, 1 = pubprodkey01.pem, 2 = pubprodkey02.pem 65 | -o, --opt OTA V1 updates only. One byte optional data expressed as a number. Default is 0. 66 | -r, --crit OTA V2 updates only. One byte optional data expressed as a number. Default is 0. 67 | -x, --meta OTA V2 updates only. An optional string to add. Multiple "--meta" options supported. 68 | Format of metastring must be: key=value 69 | 70 | KindleTool info 71 | Get the default root password 72 | 73 | notices: 74 | 1) Kindle 4.0+ has a known bug that prevents some updates with meta-strings to run. 75 | 2) Currently, even though OTA V2 supports updates that run on multiple devices, it is not possible to create a update package that will run on both the Kindle 4 (No Touch) and Kindle 5 (Kindle Touch). --------------------------------------------------------------------------------