366 lines
10 KiB
C
366 lines
10 KiB
C
/*
|
|
Copyright 2010 BartSimpson aka skindle
|
|
|
|
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.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "mobi.h"
|
|
|
|
unsigned char *getExthData(MobiFile *book, unsigned int type, unsigned int *len) {
|
|
unsigned int i;
|
|
unsigned int exthRecords = bswap_l(book->exth->recordCount);
|
|
ExthRecHeader *erh = book->exth->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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void freeMobiFile(MobiFile *book) {
|
|
free(book->hr);
|
|
free(book->record0);
|
|
free(book);
|
|
}
|
|
|
|
MobiFile *parseMobiHeader(FILE *f) {
|
|
unsigned int numRecs, i, magic;
|
|
MobiFile *book = (MobiFile*)calloc(sizeof(MobiFile), 1);
|
|
book->f = f;
|
|
if (fread(&book->pdb, sizeof(PDB), 1, f) != 1) {
|
|
fprintf(stderr, "Failed to read Palm headers\n");
|
|
free(book);
|
|
return NULL;
|
|
}
|
|
|
|
//do BOOKMOBI check
|
|
if (book->pdb.type != 0x4b4f4f42 || book->pdb.creator != 0x49424f4d) {
|
|
fprintf(stderr, "Invalid header type or creator\n");
|
|
free(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->recs = bswap_s(book->pdb.numRecs);
|
|
|
|
book->hr = (HeaderRec*)malloc(book->recs * sizeof(HeaderRec));
|
|
if (fread(book->hr, sizeof(HeaderRec), book->recs, f) != book->recs) {
|
|
fprintf(stderr, "Failed read of header record\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->record0_offset = bswap_l(book->hr[0].offset);
|
|
book->record0_size = bswap_l(book->hr[1].offset) - book->record0_offset;
|
|
|
|
if (fseek(f, book->record0_offset, SEEK_SET) == -1) {
|
|
fprintf(stderr, "bad seek to header record offset\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->record0 = (unsigned char*)malloc(book->record0_size);
|
|
|
|
if (fread(book->record0, book->record0_size, 1, f) != 1) {
|
|
fprintf(stderr, "bad read of record0\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->pdh = (PalmDocHeader*)(book->record0);
|
|
if (bswap_s(book->pdh->encryptionType) != 2) {
|
|
fprintf(stderr, "MOBI BOOK is not encrypted\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->textRecs = bswap_s(book->pdh->recordCount);
|
|
|
|
book->mobi = (MobiHeader*)(book->pdh + 1);
|
|
if (book->mobi->id != 0x49424f4d) {
|
|
fprintf(stderr, "MOBI header not found\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->mobiLen = bswap_l(book->mobi->hdrLen);
|
|
book->extra_data_flags = 0;
|
|
|
|
if (book->mobiLen >= 0xe4) {
|
|
book->extra_data_flags = bswap_s(book->mobi->extra_flags);
|
|
}
|
|
|
|
if ((bswap_l(book->mobi->exthFlags) & 0x40) == 0) {
|
|
fprintf(stderr, "Missing exth header\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
book->exth = (ExthHeader*)(book->mobiLen + (char*)(book->mobi));
|
|
if (book->exth->id != 0x48545845) {
|
|
fprintf(stderr, "EXTH header not found\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
//if you want a list of EXTH records, uncomment the following
|
|
// enumExthRecords(exth);
|
|
|
|
book->drmCount = bswap_l(book->mobi->drmCount);
|
|
|
|
if (book->drmCount == 0) {
|
|
fprintf(stderr, "no PIDs found in this file\n");
|
|
freeMobiFile(book);
|
|
return NULL;
|
|
}
|
|
|
|
return book;
|
|
}
|
|
|
|
int writeMobiOutputFile(MobiFile *book, FILE *out, unsigned char *key,
|
|
unsigned int drmOffset, unsigned int drm_len) {
|
|
int i;
|
|
struct stat statbuf;
|
|
|
|
fstat(fileno(book->f), &statbuf);
|
|
|
|
// kill the drm keys
|
|
memset(book->record0 + drmOffset, 0, drm_len);
|
|
// kill the drm pointers
|
|
book->mobi->drmOffset = 0xffffffff;
|
|
book->mobi->drmCount = book->mobi->drmSize = book->mobi->drmFlags = 0;
|
|
// clear the crypto type
|
|
book->pdh->encryptionType = 0;
|
|
|
|
fwrite(&book->pdb, sizeof(PDB), 1, out);
|
|
fwrite(book->hr, sizeof(HeaderRec), book->recs, out);
|
|
fwrite("\x00\x00", 1, 2, out);
|
|
fwrite(book->record0, book->record0_size, 1, out);
|
|
|
|
//need to zero out exth 209 data
|
|
for (i = 1; i < book->recs; i++) {
|
|
unsigned int offset = bswap_l(book->hr[i].offset);
|
|
unsigned int len, extra_size = 0;
|
|
unsigned char *rec;
|
|
if (i == (book->recs - 1)) { //last record extends to end of file
|
|
len = statbuf.st_size - offset;
|
|
}
|
|
else {
|
|
len = bswap_l(book->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(book->f, offset, SEEK_SET) != 0) {
|
|
fprintf(stderr, "Failed record seek on input\n");
|
|
freeMobiFile(book);
|
|
free(rec);
|
|
return 0;
|
|
}
|
|
if (fread(rec, len, 1, book->f) != 1) {
|
|
fprintf(stderr, "Failed record read on input\n");
|
|
freeMobiFile(book);
|
|
free(rec);
|
|
return 0;
|
|
}
|
|
|
|
if (i <= book->textRecs) { //decrypt if necessary
|
|
extra_size = getSizeOfTrailingDataEntries(rec, len, book->extra_data_flags);
|
|
PC1(key, 16, rec, rec, len - extra_size, 1);
|
|
}
|
|
fwrite(rec, len, 1, out);
|
|
free(rec);
|
|
}
|
|
return 1;
|
|
}
|