├── Makefile
├── KindleTool
├── Makefile
├── KindleTool.1
├── kindle_tool.h
├── convert.c
├── kindle_tool.c
└── create.c
├── COMPILING
├── README
└── KindleTool.xcodeproj
└── project.pbxproj
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | cd KindleTool && make
3 |
4 | clean:
5 | cd KindleTool && make clean
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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
--------------------------------------------------------------------------------
/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).
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------