You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DeDRM_tools/skindle/mobi.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;
}