From 049f44231c836bcbe5cb1f13842d1a3265984abd Mon Sep 17 00:00:00 2001 From: Bart Simpson Date: Mon, 28 Dec 2009 22:02:00 +0000 Subject: [PATCH] First Kindle 4 PC solution --- skindle/Makefile | 21 + skindle/README.txt | 78 ++++ skindle/b64.c | 80 ++++ skindle/md5.c | 381 ++++++++++++++++ skindle/md5.h | 91 ++++ skindle/sha1.c | 234 ++++++++++ skindle/sha1.h | 68 +++ skindle/skindle.c | 1039 +++++++++++++++++++++++++++++++++++++++++++ skindle/skindle.exe | Bin 0 -> 22016 bytes 9 files changed, 1992 insertions(+) create mode 100644 skindle/Makefile create mode 100644 skindle/README.txt create mode 100644 skindle/b64.c create mode 100644 skindle/md5.c create mode 100644 skindle/md5.h create mode 100644 skindle/sha1.c create mode 100644 skindle/sha1.h create mode 100644 skindle/skindle.c create mode 100644 skindle/skindle.exe diff --git a/skindle/Makefile b/skindle/Makefile new file mode 100644 index 0000000..2eb8f3c --- /dev/null +++ b/skindle/Makefile @@ -0,0 +1,21 @@ + +OBJS=skindle.o md5.o sha1.o b64.o + +CC=gcc +LD=gcc +EXE=skindle +EXTRALIBS=-lCrypt32 + +#use the following to strip your binary +LDFLAGS=-s + +all: $(EXE) + +%.o: %.c + $(CC) -c $(CFLAGS) $(INC) $< -o $@ + +$(EXE): $(OBJS) + $(LD) $(LDFLAGS) -o $@ $(OBJS) $(EXTRALIBS) + +clean: + -@rm *.o diff --git a/skindle/README.txt b/skindle/README.txt new file mode 100644 index 0000000..132844c --- /dev/null +++ b/skindle/README.txt @@ -0,0 +1,78 @@ + +/* + Copyright 2010 BartSimpson + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + * Dependencies: none + * build on cygwin: gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32 + * Under cygwin, you can just type make to build it. + * While the binary builds if you use the -mno-cygwin switch, it fails to + * work for some reason. The code should compile with Visual Studio, just + * add all the files to a project and add the Crypt32.lib dependency and + * it should build as a Win32 console app. + */ + +/* + * MUST be run on the computer on which KindleForPC is installed + * under the account that was used to purchase DRM'ed content. + * Requires your kindle.info file which can be found in something like: + * \...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY} + * where ... varies by platform but is "Local Settings\Application Data" on XP + */ + +/* + What: KindleForPC DRM removal utility to preserve your fair use rights! + Why: Fair use is a well established doctrine, and I am no fan of vendor + lockin. + How: This utility implements the PID extraction, DRM key generation and + decryption algorithms employed by the KindleForPC application. This + is a stand alone app that does not require you to determine a PID on + your own, and it does not need to run KindleForPC in order to extract + any data from memory. + + Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this + is just a C port of mobidedrm. + labba and I<3cabbages for motivating me to do this the right way. + You guys shouldn't need to spend all your time responding to all the + changes Amazon is going to force you to make in unswindle each time + the release a new version. + Lawrence Lessig - You are my hero. 'Nuff said. + Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off + of the exploitation of works out of copyright while vigourously + pushing copyright extension to prevent others from doing the same + is the height of hypocrasy. + Congress - you guys suck too. Why you arrogant pricks think you + are smarter than the founding fathers is beyond me. + */ + +Rationale: +Need a tool to enable fair use of purchased ebook content. +Need a tool that is not dependent on any particular version of +KindleForPC and that does not need to run KindleForPC in order to +extract a PID. The tool documents the structure of the kindle.info +file and the data and algorthims that are used to derive per book +PID values. + +Installing: +A cygwin compatable binary is included. You need a minimal cygwin +installation in order to run it. To build from source, you will need +cygwin with gcc and make. This has not been tested with Visual Studio. + +Usage: +./skindle +You need to locate your kindle.info file somewhere on your system. +You can copy it to a local directory, but you need to refer to it +each time you run skindle. diff --git a/skindle/b64.c b/skindle/b64.c new file mode 100644 index 0000000..74c82d2 --- /dev/null +++ b/skindle/b64.c @@ -0,0 +1,80 @@ +/*********************************************************************\ +LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall + be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +VERSION HISTORY: + Bob Trower 08/04/01 -- Create Version 0.00.00B + +\******************************************************************* */ + +#include + +/* +** Translation Table as described in RFC1113 +*/ +static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* +** encodeblock +** +** encode 3 8-bit binary bytes as 4 '6-bit' characters +*/ +void encodeblock(unsigned char in[3], unsigned char out[4], int len) { + out[0] = cb64[ in[0] >> 2 ]; + out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ]; + out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '='); + out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '='); +} + +/* +** encode +** +** base64 encode a stream adding padding and line breaks as per spec. +*/ +unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf) { + unsigned char in[3], out[4]; + int c; + unsigned int i = 0; + unsigned int outlen = 0; + while (i < len) { + int n = 0; + for(c = 0; c < 3; c++, i++) { + if (i < len) { + in[c] = inbuf[i]; + n++; + } + else { + in[c] = 0; + } + } + if (n) { + encodeblock(in, out, n); + for(c = 0; c < 4; c++) { + outbuf[outlen++] = out[c]; + } + } + } + return outlen; +} + diff --git a/skindle/md5.c b/skindle/md5.c new file mode 100644 index 0000000..c35d96c --- /dev/null +++ b/skindle/md5.c @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/skindle/md5.h b/skindle/md5.h new file mode 100644 index 0000000..698c995 --- /dev/null +++ b/skindle/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/skindle/sha1.c b/skindle/sha1.c new file mode 100644 index 0000000..cecae83 --- /dev/null +++ b/skindle/sha1.c @@ -0,0 +1,234 @@ +/* + sha1.c: Implementation of SHA-1 Secure Hash Algorithm-1 + + Based upon: NIST FIPS180-1 Secure Hash Algorithm-1 + http://www.itl.nist.gov/fipspubs/fip180-1.htm + + Non-official Japanese Translation by HIRATA Yasuyuki: + http://yasu.asuka.net/translations/SHA-1.html + + Copyright (C) 2002 vi@nwr.jp. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as beging the original software. + 3. This notice may not be removed or altered from any source distribution. + + Note: + The copyright notice above is copied from md5.h by L. Peter Deutsch + . Thank him since I'm not a good speaker of English. :) + */ +#include +#include "sha1.h" + +/* + * Packing bytes to a word + * + * Should not assume p is aligned to word boundary + */ +static sha1_word_t packup(sha1_byte_t *p) +{ + /* Portable, but slow */ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3] << 0; +} + +/* + * Unpacking a word to bytes + * + * Should not assume p is aligned to word boundary + */ +static void unpackup(sha1_byte_t *p, sha1_word_t q) +{ + p[0] = (q >> 24) & 0xff; + p[1] = (q >> 16) & 0xff; + p[2] = (q >> 8) & 0xff; + p[3] = (q >> 0) & 0xff; +} + +/* + * Processing a block + */ +static void sha1_update_now(sha1_state_s *pms, sha1_byte_t *bp) +{ + sha1_word_t tmp, a, b, c, d, e, w[16+16]; + int i, s; + + /* pack 64 bytes into 16 words */ + for(i = 0; i < 16; i++) { + w[i] = packup(bp + i * sizeof(sha1_word_t)); + } + memcpy(w + 16, w + 0, sizeof(sha1_word_t) * 16); + + a = pms->sha1_h[0], b = pms->sha1_h[1], c = pms->sha1_h[2], d = pms->sha1_h[3], e = pms->sha1_h[4]; + +#define rot(x,n) (((x) << n) | ((x) >> (32-n))) +#define f0(b, c, d) ((b&c)|(~b&d)) +#define f1(b, c, d) (b^c^d) +#define f2(b, c, d) ((b&c)|(b&d)|(c&d)) +#define f3(b, c, d) (b^c^d) +#define k0 0x5a827999 +#define k1 0x6ed9eba1 +#define k2 0x8f1bbcdc +#define k3 0xca62c1d6 + + /* t=0-15 */ + s = 0; + for(i = 0; i < 16; i++) { + tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=16-19 */ + for(i = 16; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=20-39 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f1(b, c, d) + e + w[s] + k1; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=40-59 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f2(b, c, d) + e + w[s] + k2; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=60-79 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f3(b, c, d) + e + w[s] + k3; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + pms->sha1_h[0] += a, pms->sha1_h[1] += b, pms->sha1_h[2] += c, pms->sha1_h[3] += d, pms->sha1_h[4] += e; +} + +/* + * Increment sha1_size1, sha1_size2 field of sha1_state_s + */ +static void incr(sha1_state_s *pms, int v) +{ + sha1_word_t q; + + q = pms->sha1_size1 + v * BITS; + if(q < pms->sha1_size1) { + pms->sha1_size2++; + } + pms->sha1_size1 = q; +} + +/* + * Initialize sha1_state_s as FIPS specifies + */ +void sha1_init(sha1_state_s *pms) +{ + memset(pms, 0, sizeof(*pms)); + pms->sha1_h[0] = 0x67452301; /* Initialize H[0]-H[4] */ + pms->sha1_h[1] = 0xEFCDAB89; + pms->sha1_h[2] = 0x98BADCFE; + pms->sha1_h[3] = 0x10325476; + pms->sha1_h[4] = 0xC3D2E1F0; +} + +/* + * Fill block and update output when needed + */ +void sha1_update(sha1_state_s *pms, sha1_byte_t *bufp, int length) +{ + /* Is the buffer partially filled? */ + if(pms->sha1_count != 0) { + if(pms->sha1_count + length >= (signed) sizeof(pms->sha1_buf)) { /* buffer is filled enough */ + int fil = sizeof(pms->sha1_buf) - pms->sha1_count; /* length to copy */ + + memcpy(pms->sha1_buf + pms->sha1_count, bufp, fil); + sha1_update_now(pms, pms->sha1_buf); + length -= fil; + bufp += fil; + pms->sha1_count = 0; + incr(pms, fil); + } else { + memcpy(pms->sha1_buf + pms->sha1_count, bufp, length); + pms->sha1_count += length; + incr(pms, length); + return; + } + } + + /* Loop to update state */ + for(;;) { + if(length < (signed) sizeof(pms->sha1_buf)) { /* Short to fill up the buffer */ + if(length) { + memcpy(pms->sha1_buf, bufp, length); + } + pms->sha1_count = length; + incr(pms, length); + break; + } + sha1_update_now(pms, bufp); + length -= sizeof(pms->sha1_buf); + bufp += sizeof(pms->sha1_buf); + incr(pms, sizeof(pms->sha1_buf)); + } +} + +void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]) +{ + int i; + sha1_byte_t buf[1]; + + /* fill a bit */ + buf[0] = 0x80; + sha1_update(pms, buf, 1); + + /* Decrement sha1_size1, sha1_size2 */ + if((pms->sha1_size1 -= BITS) == 0) { + pms->sha1_size2--; + } + + /* fill zeros */ + if(pms->sha1_count > (signed) (sizeof(pms->sha1_buf) - 2 * sizeof(sha1_word_t))) { + memset(pms->sha1_buf + pms->sha1_count, 0, sizeof(pms->sha1_buf) - pms->sha1_count); + sha1_update_now(pms, pms->sha1_buf); + pms->sha1_count = 0; + } + memset(pms->sha1_buf + pms->sha1_count, 0, + sizeof(pms->sha1_buf) - pms->sha1_count - sizeof(sha1_word_t) * 2); + + /* fill last length */ + unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 2, pms->sha1_size2); + unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 1, pms->sha1_size1); + + /* final update */ + sha1_update_now(pms, pms->sha1_buf); + + /* move hash value to output byte array */ + for(i = 0; i < (signed) (sizeof(pms->sha1_h)/sizeof(sha1_word_t)); i++) { + unpackup(output + i * sizeof(sha1_word_t), pms->sha1_h[i]); + } +} diff --git a/skindle/sha1.h b/skindle/sha1.h new file mode 100644 index 0000000..bae61e0 --- /dev/null +++ b/skindle/sha1.h @@ -0,0 +1,68 @@ +/* + sha1.h: Implementation of SHA-1 Secure Hash Algorithm-1 + + Based upon: NIST FIPS180-1 Secure Hash Algorithm-1 + http://www.itl.nist.gov/fipspubs/fip180-1.htm + + Non-official Japanese Translation by HIRATA Yasuyuki: + http://yasu.asuka.net/translations/SHA-1.html + + Copyright (C) 2002 vi@nwr.jp. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as beging the original software. + 3. This notice may not be removed or altered from any source distribution. + + Note: + The copyright notice above is copied from md5.h by L. Petet Deutsch + . Thank him since I'm not a good speaker of English. :) + */ +#ifndef SHA1_H +#define SHA1_H + +typedef unsigned int sha1_word_t; /* 32bits unsigned integer */ +typedef unsigned char sha1_byte_t; /* 8bits unsigned integer */ +#define BITS 8 + +/* Define the state of SHA-1 algorithm */ +typedef struct { + sha1_byte_t sha1_buf[64]; /* 512 bits */ + int sha1_count; /* How many bytes are used */ + sha1_word_t sha1_size1; /* Length counter Lower Word */ + sha1_word_t sha1_size2; /* Length counter Upper Word */ + sha1_word_t sha1_h[5]; /* Hash output */ +} sha1_state_s; +#define SHA1_OUTPUT_SIZE 20 /* in bytes */ + +/* External Functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize SHA-1 algorithm */ +void sha1_init(sha1_state_s *pms); + +/* Append a string to SHA-1 algorithm */ +void sha1_update(sha1_state_s *pms, sha1_byte_t *input_buffer, int length); + +/* Finish the SHA-1 algorithm and return the hash */ +void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/skindle/skindle.c b/skindle/skindle.c new file mode 100644 index 0000000..ee669b9 --- /dev/null +++ b/skindle/skindle.c @@ -0,0 +1,1039 @@ + +/* + Copyright 2010 BartSimpson + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + * Dependencies: none + * build on cygwin: gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32 + * Under cygwin, you can just type make to build it. + * While the binary builds if you use the -mno-cygwin switch, it fails to + * work for some reason. The code should compile with Visual Studio, just + * add all the files to a project and add the Crypt32.lib dependency and + * it should build as a Win32 console app. + */ + +/* + * MUST be run on the computer on which KindleForPC is installed + * under the account that was used to purchase DRM'ed content. + * Requires your kindle.info file which can be found in something like: + * \...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY} + * where ... varies by platform but is "Local Settings\Application Data" on XP + */ + +/* + What: KindleForPC DRM removal utility to preserve your fair use rights! + Why: Fair use is a well established doctrine, and I am no fan of vendor + lockin. + How: This utility implements the PID extraction, DRM key generation and + decryption algorithms employed by the KindleForPC application. This + is a stand alone app that does not require you to determine a PID on + your own, and it does not need to run KindleForPC in order to extract + any data from memory. + + Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this + is just a C port of mobidedrm. + labba and I<3cabbages for motivating me to do this the right way. + You guys shouldn't need to spend all your time responding to all the + changes Amazon is going to force you to make in unswindle each time + the release a new version. + Lawrence Lessig - You are my hero. 'Nuff said. + Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off + of the exploitation of works out of copyright while vigourously + pushing copyright extension to prevent others from doing the same + is the height of hypocrasy. + Congress - you guys suck too. Why you arrogant pricks think you + are smarter than the founding fathers is beyond me. + */ + +#include +#include + +#include +#include +#include +#include +#include + +//If you prefer to use openssl uncomment the following +//#include +//#include + +//If you prefer to use openssl remove the following 2 line +#include "md5.h" +#include "sha1.h" + +unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf); + +/* The kindle.info file created when you install KindleForPC is a set + * of key:value pairs delimited by '{'. The keys and values are encoded + * in a variety of ways. Keys are the mazama64 encoded md5 hash of the + * key name, while values are the mazama64 encoding of the blob returned + * by the Windows CryptProtectData function. The use of CryptProtectData + * is what locks things to a particular user/machine + + * kindle.info layout + + * Key:AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6 ("kindle.account.tokens") + * Value: mazama64Encode(CryptProtectData(some sha1 hash)) + + * Key:AsAWa4ZJAQaCZ7A3zrZSaZavZMarZFAw ("kindle.cookie.item") + * Value: mazama64Encode(CryptProtectData(base64(144 bytes of data))) + + * Key:ZHatAla4a-zTzWA-AvaeAvZQzKZ-agAz ("eulaVersionAccepted") + * Value: mazama64Encode(CryptProtectData(kindle version?)) + + * Key:ZiajZga7Z9zjZRz7AfZ-zRzUANZNZJzP ("login_date") + * Value: mazama64Encode(CryptProtectData(registration date)) + + * Key:ZkzeAUA-Z2ZYA2Z_ayA_ahZEATaEAOaG ("kindle.token.item") + * Value: mazama64Encode(CryptProtectData(multi-field crypto data)) + * {enc:xxx}{iv:xxx}{key:xxx}{name:xxx}{serial:xxx} + * enc:base64(binary blob) + * iv:base64(16 bytes) + * key:base64(256 bytes) + * name:base64("ADPTokenEncryptionKey") + * serial:base64("1") + + * Key:aVzrzRAFZ7aIzmASZOzVzIAGAKawzwaU ("login") + * Value: mazama64Encode(CryptProtectData(your amazon email)) + + * Key:avalzbzkAcAPAQA5ApZgaOZPzQZzaiaO mazama64Encode(md5("MazamaRandomNumber")) + * Value: mazama64Encode(CryptProtectData(mazama32Encode(32 bytes random data))) + + * Key:zgACzqAjZ2zzAmAJa6ZFaZALaYAlZrz- ("kindle.key.item") + * Value: mazama64Encode(CryptProtectData(RSA private key)) no password + + * Key:zga-aIANZPzbzfZ1zHZWZcA4afZMZcA_ ("kindle.name.info") + * Value: mazama64Encode(CryptProtectData(your name)) + + * Key:zlZ9afz1AfAVZjacaqa-ZHa1aIa_ajz7 ("kindle.device.info"); + * Value: mazama64Encode(CryptProtectData(the name of your kindle)) +*/ + +typedef struct _SimpleMapNode { + char *key; + char *value; +} SimpleMapNode; + +typedef struct _MapList { + SimpleMapNode *node; + struct _MapList *next; +} MapList; + +MapList *kindleMap; + +#pragma pack(2) +typedef struct _PDB { + char name[32]; + unsigned short attrib; + unsigned short version; + unsigned int created; + unsigned int modified; + unsigned int backup; + unsigned int modNum; + unsigned int appInfoID; + unsigned int sortInfoID; + unsigned int type; + unsigned int creator; + unsigned int uniqueIDseed; + unsigned int nextRecordListID; + unsigned short numRecs; +} PDB; + +typedef struct _HeaderRec { + unsigned int offset; + unsigned int attribId; +} HeaderRec; + +#define attrib(x) ((x)&0xFF) +#define id(x) (bswap_l((x) & 0xFFFFFF00)) + +typedef struct _PalmDocHeader { + unsigned short compression; + unsigned short reserverd1; + unsigned int textLength; + unsigned short recordCount; + unsigned short recordSize; + unsigned short encryptionType; + unsigned short reserved2; +} PalmDocHeader; + + +//checked lengths are 24, 116, 208, 228 +typedef struct _MobiHeader { + unsigned int id; + unsigned int hdrLen; + unsigned int type; + unsigned int encoding; + unsigned int uniqueId; + unsigned int generator; + unsigned char reserved1[40]; + unsigned int firstNonBookIdx; + unsigned int nameOffset; + unsigned int nameLength; + unsigned int language; + unsigned int inputLang; + unsigned int outputLang; + unsigned int formatVersion; + unsigned int firstImageIdx; + unsigned char unknown1[16]; + unsigned int exthFlags; + unsigned char unknown2[36]; + unsigned int drmOffset; + unsigned int drmCount; + unsigned int drmSize; + unsigned int drmFlags; + unsigned char unknown3[58]; + unsigned short extra_flags; +} MobiHeader; + +typedef struct _ExthRecHeader { + unsigned int type; + unsigned int len; +} ExthRecHeader; + +typedef struct _ExthHeader { + unsigned int id; + unsigned int hdrLen; + unsigned int recordCount; + ExthRecHeader records[1]; +} ExthHeader; + + +//#define bswap_l ntohl +//#define bswap_s ntohs + +unsigned short bswap_s(unsigned short s) { + return (s >> 8) | (s << 8); +} + +unsigned int bswap_l(unsigned int s) { + unsigned int u = bswap_s(s); + unsigned int l = bswap_s(s >> 16); + return (u << 16) | l; +} + +MapList *findNode(char *key) { + MapList *l; + for (l = kindleMap; l; l = l->next) { + if (strcmp(key, l->node->key) == 0) { + return l; + } + } + return NULL; +} + +void addMapNode(char *key, char *value) { + MapList *ml = findNode(key); + if (ml) { + free(ml->node->value); + ml->node->value = value; + } + else { + SimpleMapNode *smn = (SimpleMapNode*)malloc(sizeof(SimpleMapNode)); + smn->key = key; + smn->value = value; + ml = (MapList*)malloc(sizeof(MapList)); + ml->node = smn; + ml->next = kindleMap; + kindleMap = ml; + } +} + +void dumpMap() { + MapList *l; + for (l = kindleMap; l; l = l->next) { + fprintf(stderr, "%s:%s\n", l->node->key, l->node->value); + } +} + +void parseLine(char *line) { + char *colon = strchr(line, ':'); + if (colon) { + char *key, *value; + int len = colon - line; + key = (char*)malloc(len + 1); + *colon++ = 0; + strcpy(key, line); + len = strlen(colon); + value = (char*)malloc(len + 1); + strcpy(value, colon); + value[len] = 0; + addMapNode(key, value); + } +} + +int buildKindleMap(char *infoFile) { + int result = 0; + struct stat statbuf; + if (stat(infoFile, &statbuf) == 0) { + FILE *fd = fopen(infoFile, "rb"); + char *infoBuf = (char*)malloc(statbuf.st_size + 1); + infoBuf[statbuf.st_size] = 0; + if (fread(infoBuf, statbuf.st_size, 1, fd) == 1) { + char *end = infoBuf + statbuf.st_size; + char *b = infoBuf, *e; + while (e = strchr(b, '{')) { + *e = 0; + if ((e - b) > 2) { + parseLine(b); + } + e++; + b = e; + } + if (b < end) { + parseLine(b); + } + } + else { + fprintf(stderr, "short read on info file\n"); + } + free(infoBuf); + fclose(fd); + return 1; + } + return 0; +} + +png_crc_table_init(unsigned int *crc_table) { + unsigned int i; + for (i = 0; i < 256; i++) { + unsigned int n = i; + unsigned int j; + for (j = 0; j < 8; j++) { + if (n & 1) { + n = 0xEDB88320 ^ (n >> 1); + } + else { + n >>= 1; + } + } + crc_table[i] = n; + } +} + +unsigned int compute_png_crc(unsigned char *input, unsigned int len, unsigned int *crc_table) { + unsigned int crc = 0; + unsigned int i; + for (i = 0; i < len; i++) { + unsigned int v = (input[i] ^ crc) & 0xff; + crc = crc_table[v] ^ (crc >> 8); + } + return crc; +} + +char *decodeString = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"; + +void doPngDecode(unsigned char *input, unsigned int len, unsigned char *output) { + unsigned int crc_table[256]; + unsigned int crc, i, x = 0; + unsigned int *out = (unsigned int*)output; + png_crc_table_init(crc_table); + crc = bswap_l(compute_png_crc(input, len, crc_table)); + memset(output, 0, 8); + for (i = 0; i < len; i++) { + output[x++] ^= input[i]; + if (x == 8) x = 0; + } + out[0] ^= crc; + out[1] ^= crc; + for (i = 0; i < 8; i++) { + unsigned char v = output[i]; + output[i] = decodeString[((((v >> 5) & 3) ^ v) & 0x1F) + (v >> 7)]; + } +} + +char *string_20 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"; +char *string_40 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"; + +char *mazamaEncode(unsigned char *input, unsigned int len, unsigned char choice) { + unsigned int i; + char *enc, *out; + if (choice == 0x20) enc = string_20; + else if (choice == 0x40) enc = string_40; + else return NULL; + out = (char*)malloc(len * 2 + 1); + out[len * 2] = 0; + for (i = 0; i < len; i++) { + unsigned char v = input[i] + 128; + unsigned char q = v / choice; + unsigned char m = v % choice; + out[i * 2] = enc[q]; + out[i * 2 + 1] = enc[m]; + } + return out; +} + +unsigned char *mazamaDecode(char *input, int *outlen) { + unsigned char *out; + int len = strlen(input); + char *dec = NULL; + int i, choice = 0x20; + *outlen = 0; + for (i = 0; i < 8 && i < len; i++) { + if (*input == string_20[i]) { + dec = string_20; + break; + } + } + if (dec == NULL) { + for (i = 0; i < 4 && i < len; i++) { + if (*input == string_40[i]) { + dec = string_40; + choice = 0x40; + break; + } + } + } + if (dec == NULL) { + return NULL; + } + out = (unsigned char*)malloc(len / 2 + 1); + out[len / 2] = 0; + for (i = 0; i < len; i += 2) { + int q, m, v; + char *p = strchr(dec, input[i]); + if (p == NULL) break; + q = p - dec; + p = strchr(dec, input[i + 1]); + if (p == NULL) break; + m = p - dec; + v = (choice * q + m) - 128; + out[(*outlen)++] = (unsigned char)v; + } + return out; +} + +unsigned char *getExthData(ExthHeader *eh, unsigned int type, unsigned int *len) { + unsigned int i; + unsigned int exthRecords = bswap_l(eh->recordCount); + ExthRecHeader *erh = eh->records; + + *len = 0; + + for (i = 0; i < exthRecords; i++) { + unsigned int recType = bswap_l(erh->type); + unsigned int recLen = bswap_l(erh->len); + + if (recLen < 8) { + fprintf(stderr, "Invalid exth record length %d\n", i); + return NULL; + } + + if (recType == type) { + *len = recLen - 8; + return (unsigned char*)(erh + 1); + } + erh = (ExthRecHeader*)(recLen + (char*)erh); + } + return NULL; +} + +void enumExthRecords(ExthHeader *eh) { + unsigned int exthRecords = bswap_l(eh->recordCount); + unsigned int i; + unsigned char *data; + ExthRecHeader *erh = eh->records; + + for (i = 0; i < exthRecords; i++) { + unsigned int recType = bswap_l(erh->type); + unsigned int recLen = bswap_l(erh->len); + + fprintf(stderr, "%d: type - %d, len %d\n", i, recType, recLen); + + if (recLen < 8) { + fprintf(stderr, "Invalid exth record length %d\n", i); + return; + } + + data = (unsigned char*)(erh + 1); + switch (recType) { + case 1: //drm_server_id + fprintf(stderr, "drm_server_id: %s\n", data); + break; + case 2: //drm_commerce_id + fprintf(stderr, "drm_commerce_id: %s\n", data); + break; + case 3: //drm_ebookbase_book_id + fprintf(stderr, "drm_ebookbase_book_id: %s\n", data); + break; + case 100: //author + fprintf(stderr, "author: %s\n", data); + break; + case 101: //publisher + fprintf(stderr, "publisher: %s\n", data); + break; + case 106: //publishingdate + fprintf(stderr, "publishingdate: %s\n", data); + break; + case 113: //asin + fprintf(stderr, "asin: %s\n", data); + break; + case 208: //book unique drm key + fprintf(stderr, "book drm key: %s\n", data); + break; + case 503: //updatedtitle + fprintf(stderr, "updatedtitle: %s\n", data); + break; + default: + break; + } + erh = (ExthRecHeader*)(recLen + (char*)erh); + } + +} + +//implementation of Pukall Cipher 1 +unsigned char *PC1(unsigned char *key, unsigned int klen, unsigned char *src, + unsigned char *dest, unsigned int len, int decryption) { + unsigned int sum1 = 0; + unsigned int sum2 = 0; + unsigned int keyXorVal = 0; + unsigned short wkey[8]; + unsigned int i; + if (klen != 16) { + fprintf(stderr, "Bad key length!\n"); + return NULL; + } + for (i = 0; i < 8; i++) { + wkey[i] = (key[i * 2] << 8) | key[i * 2 + 1]; + } + for (i = 0; i < len; i++) { + unsigned int temp1 = 0; + unsigned int byteXorVal = 0; + unsigned int j, curByte; + for (j = 0; j < 8; j++) { + temp1 ^= wkey[j]; + sum2 = (sum2 + j) * 20021 + sum1; + sum1 = (temp1 * 346) & 0xFFFF; + sum2 = (sum2 + sum1) & 0xFFFF; + temp1 = (temp1 * 20021 + 1) & 0xFFFF; + byteXorVal ^= temp1 ^ sum2; + } + curByte = src[i]; + if (!decryption) { + keyXorVal = curByte * 257; + } + curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF; + if (decryption) { + keyXorVal = curByte * 257; + } + for (j = 0; j < 8; j++) { + wkey[j] ^= keyXorVal; + } + dest[i] = curByte; + } + return dest; +} + +unsigned int getSizeOfTrailingDataEntry(unsigned char *ptr, unsigned int size) { + unsigned int bitpos = 0; + unsigned int result = 0; + if (size <= 0) { + return result; + } + while (1) { + unsigned int v = ptr[size - 1]; + result |= (v & 0x7F) << bitpos; + bitpos += 7; + size -= 1; + if ((v & 0x80) != 0 || (bitpos >= 28) || (size == 0)) { + return result; + } + } +} + +unsigned int getSizeOfTrailingDataEntries(unsigned char *ptr, unsigned int size, unsigned int flags) { + unsigned int num = 0; + unsigned int testflags = flags >> 1; + while (testflags) { + if (testflags & 1) { + num += getSizeOfTrailingDataEntry(ptr, size - num); + } + testflags >>= 1; + } + if (flags & 1) { + num += (ptr[size - num - 1] & 0x3) + 1; + } + return num; +} + +typedef struct _vstruct { + unsigned int verification; + unsigned int size; + unsigned int type; + unsigned char cksum[4]; + unsigned char cookie[32]; +} vstruct; + +typedef struct _kstruct { + unsigned int ver; + unsigned int flags; + unsigned char finalkey[16]; + unsigned int expiry; + unsigned int expiry2; +} kstruct; + +unsigned char *parseDRM(unsigned char *data, unsigned int count, unsigned char *pid, unsigned int pidlen) { + unsigned int i; + unsigned char temp_key_sum = 0; + unsigned char *found_key = NULL; + unsigned char *keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"; + unsigned char temp_key[16]; + + memset(temp_key, 0, 16); + memcpy(temp_key, pid, 8); + PC1(keyvec1, 16, temp_key, temp_key, 16, 0); + + for (i = 0; i < 16; i++) { + temp_key_sum += temp_key[i]; + } + + for (i = 0; i < count; i++) { + unsigned char kk[32]; + vstruct *v = (vstruct*)(data + i * 0x30); + kstruct *k = (kstruct*)PC1(temp_key, 16, v->cookie, kk, 32, 1); + + if (v->verification == k->ver && v->cksum[0] == temp_key_sum && + (bswap_l(k->flags) & 0x1F) == 1) { + found_key = (unsigned char*)malloc(16); + memcpy(found_key, k->finalkey, 16); + break; + } + } + return found_key; +} + +#ifndef HEADER_MD5_H + +#define MD5_DIGEST_LENGTH 16 + +#define MD5_CTX md5_state_t +#define MD5_Init md5_init +#define MD5_Update md5_append +#define MD5_Final(x, y) md5_finish(y, x) +#define MD5 md5 + +void md5(unsigned char *in, int len, unsigned char *md) { + MD5_CTX s; + MD5_Init(&s); + MD5_Update(&s, in, len); + MD5_Final(md, &s); +} +#endif + +#ifndef HEADER_SHA_H + +#define SHA_DIGEST_LENGTH 20 +#define SHA_CTX sha1_state_s +#define SHA1_Init sha1_init +#define SHA1_Update sha1_update +#define SHA1_Final(x, y) sha1_finish(y, x) +#define SHA1 sha1 + +void sha1(unsigned char *in, int len, unsigned char *md) { + SHA_CTX s; + SHA1_Init(&s); + SHA1_Update(&s, in, len); + SHA1_Final(md, &s); +} +#endif + +int main(int argc, char **argv) { + //had to pile all these up here to please VS2009 + PDB header; + struct stat statbuf; + FILE *prc, *out; + HeaderRec *hr; + PalmDocHeader *pdh; + MobiHeader *mobi; + ExthHeader *exth; + long record0_offset; + unsigned int record0_size, mobiLen, extra_data_flags; + unsigned int recs, i, drmCount, len209; + unsigned char *record0, *d209; + unsigned char *vsn, *username, *mrn_key, *kat_key; + char drive[256]; + char name[256]; + DWORD nlen = sizeof(name); + char *d; + char volumeName[256]; + DWORD volumeSerialNumber; + char fileSystemNameBuffer[256]; + char volumeID[32]; + unsigned char md5sum[MD5_DIGEST_LENGTH]; + unsigned char sha1sum[SHA_DIGEST_LENGTH]; + unsigned char pid_base64[SHA_DIGEST_LENGTH * 2]; + unsigned int outl; + SHA_CTX sha1_ctx; + MapList *ml; + unsigned int drmOffset, drm_len; + unsigned char *drm, *found_key; + + if (argc != 4) { + fprintf(stderr, "usage: %s\n ", argv[0]); + exit(1); + } + + if (stat(argv[1], &statbuf) != 0) { + fprintf(stderr, "Unable to stat %s, quitting\n", argv[1]); + exit(1); + } + + prc = fopen(argv[1], "rb"); + if (prc == NULL) { + fprintf(stderr, "%s bad open, quitting\n", argv[1]); + exit(1); + } + + if (fread(&header, sizeof(header), 1, prc) != 1) { + fprintf(stderr, "%s bad header read, quitting\n", argv[1]); + fclose(prc); + exit(1); + } + + //do BOOKMOBI check + if (header.type != 0x4b4f4f42 || header.creator != 0x49424f4d) { + fprintf(stderr, "Invalid header type or creator, quitting\n"); + fclose(prc); + exit(1); + } + + if (!buildKindleMap(argv[3])) { + fprintf(stderr, "buildMap failed\n"); + fclose(prc); + exit(1); + } + + recs = bswap_s(header.numRecs); + + hr = (HeaderRec*)malloc(recs * sizeof(HeaderRec)); + if (fread(hr, sizeof(HeaderRec), recs, prc) != recs) { + fprintf(stderr, "Failed read of header record, quitting\n"); + fclose(prc); + exit(1); + } + + record0_offset = bswap_l(hr[0].offset); + record0_size = bswap_l(hr[1].offset) - record0_offset; + + if (fseek(prc, record0_offset, SEEK_SET) == -1) { + fprintf(stderr, "bad seek to header record offset, quitting\n"); + fclose(prc); + exit(1); + } + + record0 = (unsigned char*)malloc(record0_size); + + if (fread(record0, record0_size, 1, prc) != 1) { + fprintf(stderr, "bad read of record0, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + pdh = (PalmDocHeader*)record0; + if (bswap_s(pdh->encryptionType) != 2) { + fprintf(stderr, "MOBI BOOK is not encrypted, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + mobi = (MobiHeader*)(pdh + 1); + if (mobi->id != 0x49424f4d) { + fprintf(stderr, "MOBI header not found, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + mobiLen = bswap_l(mobi->hdrLen); + extra_data_flags = 0; + + if (mobiLen >= 0xe4) { + extra_data_flags = bswap_s(mobi->extra_flags); + } + + if ((bswap_l(mobi->exthFlags) & 0x40) == 0) { + fprintf(stderr, "Missing exth header, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + exth = (ExthHeader*)(mobiLen + (char*)mobi); + if (exth->id != 0x48545845) { + fprintf(stderr, "EXTH header not found\n"); + free(record0); + fclose(prc); + exit(1); + } + + //if you want a list of EXTH records, uncomment the following +// enumExthRecords(exth); + + drmCount = bswap_l(mobi->drmCount); + + if (drmCount == 0) { + fprintf(stderr, "no PIDs found in this file, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + if (GetUserName(name, &nlen) == 0) { + fprintf(stderr, "GetUserName failed, quitting\n"); + fclose(prc); + exit(1); + } + fprintf(stderr, "Using UserName = \"%s\"\n", name); + + d = getenv("SystemDrive"); + if (d) { + strcpy(drive, d); + strcat(drive, "\\"); + } + else { + strcpy(drive, "c:\\"); + } + fprintf(stderr, "Using SystemDrive = \"%s\"\n", drive); + if (GetVolumeInformation(drive, volumeName, sizeof(volumeName), &volumeSerialNumber, + NULL, NULL, fileSystemNameBuffer, sizeof(fileSystemNameBuffer))) { + sprintf(volumeID, "%u", volumeSerialNumber); + } + else { + strcpy(volumeID, "9999999999"); + } + fprintf(stderr, "Using VolumeSerialNumber = \"%s\"\n", volumeID); + MD5(volumeID, strlen(volumeID), md5sum); + vsn = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20); + + MD5(name, strlen(name), md5sum); + username = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20); + + MD5("MazamaRandomNumber", 18, md5sum); + mrn_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40); + + MD5("kindle.account.tokens", 21, md5sum); + kat_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40); + + SHA1_Init(&sha1_ctx); + + ml = findNode(mrn_key); + if (ml) { + DATA_BLOB DataIn; + DATA_BLOB DataOut; + DataIn.pbData = mazamaDecode(ml->node->value, (int*)&DataIn.cbData); + if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) { + char *devId = (char*)malloc(DataOut.cbData + 2 * MD5_DIGEST_LENGTH + 1); + char *finalDevId; + unsigned char pidbuf[10]; + +// fprintf(stderr, "CryptUnprotectData success\n"); +// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr); +// fprintf(stderr, "\n"); + + memcpy(devId, DataOut.pbData, DataOut.cbData); + strcpy(devId + DataOut.cbData, vsn); + strcat(devId + DataOut.cbData, username); + +// fprintf(stderr, "Computing sha1 over %d bytes\n", DataOut.cbData + 4 * MD5_DIGEST_LENGTH); + sha1(devId, DataOut.cbData + 4 * MD5_DIGEST_LENGTH, sha1sum); + finalDevId = mazamaEncode(sha1sum, SHA_DIGEST_LENGTH, 0x20); +// fprintf(stderr, "finalDevId: %s\n", finalDevId); + + SHA1_Update(&sha1_ctx, finalDevId, strlen(finalDevId)); + + pidbuf[8] = 0; + doPngDecode(finalDevId, 4, (unsigned char*)pidbuf); +// fprintf(stderr, "Device PID: %s\n", pidbuf); + + LocalFree(DataOut.pbData); + free(devId); + free(finalDevId); + } + else { + fprintf(stderr, "CryptUnprotectData failed, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + free(DataIn.pbData); + } + + ml = findNode(kat_key); + if (ml) { + DATA_BLOB DataIn; + DATA_BLOB DataOut; + DataIn.pbData = mazamaDecode(ml->node->value, (int*)&DataIn.cbData); + if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) { +// fprintf(stderr, "CryptUnprotectData success\n"); +// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr); +// fprintf(stderr, "\n"); + + SHA1_Update(&sha1_ctx, DataOut.pbData, DataOut.cbData); + + LocalFree(DataOut.pbData); + } + else { + fprintf(stderr, "CryptUnprotectData failed, quitting\n"); + fclose(prc); + free(record0); + exit(1); + } + + free(DataIn.pbData); + } + + d209 = getExthData(exth, 209, &len209); + + if (d209 != NULL) { + unsigned char *rec; + unsigned int idx; + SHA1_Update(&sha1_ctx, d209, len209); + for (idx = 0; idx < len209; idx += 5) { + unsigned int dlen; + unsigned int rtype = bswap_l(*(unsigned int*)(d209 + idx + 1)); + rec = getExthData(exth, rtype, &dlen); + if (rec != NULL) { +// fprintf(stderr, "exth %d: %s\n", rtype, rec); + SHA1_Update(&sha1_ctx, rec, dlen); + } + } + } + + SHA1_Final(sha1sum, &sha1_ctx); + + outl = base64(sha1sum, SHA_DIGEST_LENGTH, pid_base64); + + pid_base64[8] = 0; + fprintf(stderr, "PID for %s is: %s\n", header.name, pid_base64); + +/* + unique pid is computed as: + base64(sha1(idArray . kindleToken . 209_data . 209_tokens)) +*/ + + free(mrn_key); + free(kat_key); + free(vsn); + free(username); + + drmOffset = bswap_l(mobi->drmOffset); + + drm_len = bswap_l(mobi->drmSize); + drm = record0 + drmOffset; + + found_key = parseDRM(drm, drmCount, pid_base64, 8); + if (found_key) { + fprintf(stderr, "Found a DRM key!\n"); + } + else { + fprintf(stderr, "Failed to find DRM key\n"); + free(record0); + fclose(prc); + exit(1); + } + + // kill the drm keys + memset(record0 + drmOffset, 0, drm_len); + // kill the drm pointers + mobi->drmOffset = 0xffffffff; + mobi->drmCount = mobi->drmSize = mobi->drmFlags = 0; + // clear the crypto type + pdh->encryptionType = 0; + + out = fopen(argv[2], "wb"); + if (out == NULL) { + fprintf(stderr, "Failed to open output file, quitting\n"); + free(record0); + fclose(prc); + exit(1); + } + + fwrite(&header, sizeof(header), 1, out); + fwrite(hr, sizeof(HeaderRec), recs, out); + fwrite("\x00\x00", 1, 2, out); + fwrite(record0, record0_size, 1, out); + + //need to zero out exth 209 data + for (i = 1; i < recs; i++) { + unsigned int offset = bswap_l(hr[i].offset); + unsigned int len, extra_size; + unsigned char *rec; + if (i == (recs - 1)) { //last record extends to end of file + len = statbuf.st_size - offset; + } + else { + len = bswap_l(hr[i + 1].offset) - offset; + } + //make sure we are at proper offset + while (ftell(out) < offset) { + fwrite("\x00", 1, 1, out); + } + rec = (unsigned char *)malloc(len); + if (fseek(prc, offset, SEEK_SET) != 0) { + fprintf(stderr, "Failed record seek on input, quitting\n"); + free(record0); + free(rec); + fclose(prc); + fclose(out); + _unlink(argv[2]); + exit(1); + } + if (fread(rec, len, 1, prc) != 1) { + fprintf(stderr, "Failed record read on input, quitting\n"); + free(record0); + free(rec); + fclose(prc); + fclose(out); + _unlink(argv[2]); + exit(1); + } + + extra_size = getSizeOfTrailingDataEntries(rec, len, extra_data_flags); + PC1(found_key, 16, rec, rec, len - extra_size, 1); + fwrite(rec, len, 1, out); + free(rec); + } + fprintf(stderr, "Done! Enjoy!\n"); + +/* + //The following loop dumps the contents of your kindle.info file + for (ml = kindleMap; ml; ml = ml->next) { + DATA_BLOB DataIn; + DATA_BLOB DataOut; + DataIn.pbData = mazamaDecode(ml->node->value, (int*)&DataIn.cbData); + if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) { + fprintf(stderr, "CryptUnprotectData success for key: %s\n", ml->node->key); + fwrite(DataOut.pbData, DataOut.cbData, 1, stderr); + fprintf(stderr, "\n"); + LocalFree(DataOut.pbData); + } + else { + fprintf(stderr, "CryptUnprotectData failed\n"); + } + free(DataIn.pbData); + } +*/ + + fclose(prc); + fclose(out); + free(record0); + return 0; +} diff --git a/skindle/skindle.exe b/skindle/skindle.exe new file mode 100644 index 0000000000000000000000000000000000000000..3e47d1b26f138b51b519b51ad978b0ab3b3bb11f GIT binary patch literal 22016 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