hacks/dump-imessages/iphone-dataprotection/img3fs/img3fs.c

466 lines
13 KiB
C

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <openssl/evp.h>
#define _FILE_OFFSET_BITS 64
#define FUSE_USE_VERSION 26
#include <fuse.h>
#define MAX_IMG3_ELEMENTS 15
struct img3_config
{
char *img3filename;
char *iv;
char *key;
};
#define MYFS_OPT(t, p, v) { t, offsetof(struct img3_config, p), v }
static struct fuse_opt img3_opts[] = {
MYFS_OPT("-iv %s", iv, 0),
MYFS_OPT("-key %s", key, 0),
FUSE_OPT_END
};
typedef struct IMG3Header
{
uint32_t magic;
uint32_t fullSize;
uint32_t sizeNoPack;
uint32_t sigCheckArea;
uint32_t iden;
} __attribute__((packed)) IMG3Header;
typedef struct IMG3Element
{
uint32_t magic;
uint32_t total_length;
uint32_t data_length;
uint32_t offset;
char name[10];
} __attribute__((packed)) IMG3Element;
typedef struct KBAG
{
uint32_t cryptState;// 1 if the key and IV in the KBAG are encrypted with the GID-key
// 2 is used with a second KBAG for the S5L8920, use is unknown.
uint32_t aesType; // 0x80 = aes-128, 0xc0 = aes-192, 0x100 = aes256
char EncIV[16];
union
{
char EncKey128[16];
char EncKey192[24];
char EncKey256[32];
} key;
} KBAG;
typedef struct IMG3
{
int fd;
uint8_t* mmap;
uint32_t aesType;//0=no aes, 0x80 = aes-128, 0xc0 = aes-192, 0x100 = aes256
const EVP_CIPHER* cipherType;
uint8_t iv[16];
uint8_t key[32];
uint8_t* decrypted_data;
int data_was_modified;
uint32_t size;
struct IMG3Header header;
uint32_t num_elements;
struct IMG3Element* data_element;
struct IMG3Element elements[MAX_IMG3_ELEMENTS];
} IMG3;
void hexToBytes(const char* hex, uint8_t* buffer, size_t bufferLen) {
size_t i;
for(i = 0; i < bufferLen && *hex != '\0'; i++) {
uint32_t byte;
sscanf(hex, "%02x", &byte);
buffer[i] = byte;
hex += 2;
}
}
/**
Check for magic constants/strings in decrypted data to get an idea if the IV/key were ok
**/
char* img3_check_decrypted_data(const uint8_t* buffer, uint32_t len)
{
if (len > 16 && !strncmp("complzss", buffer, 8))
{
return "kernelcache";
}
if (len > 0x800 && !strncmp("H+", &buffer[0x400], 2))
{
return "ramdisk";
}
if (len > 0x300 && !strncmp("iBoot", &buffer[0x280], 5))
{
return "bootloader";
}
//TODO devicetree, logos
return NULL;
}
IMG3* img3_init(struct img3_config* config)
{
IMG3* img3 = NULL;
struct stat st;
uint32_t len,offset,i,keylen;
if(stat(config->img3filename, &st) == -1)
{
perror("stat");
return NULL;
}
len = st.st_size;
int fd = open(config->img3filename, O_RDWR);
if (fd == -1)
{
perror("open");
return NULL;
}
img3 = malloc(sizeof(IMG3));
if (img3 == NULL)
{
perror("malloc");
return NULL;
}
img3->fd = fd;
img3->size = len;
img3->num_elements = 0;
img3->aesType = 0;
img3->data_was_modified = 0;
img3->cipherType = NULL;
img3->decrypted_data = NULL;
img3->data_element = NULL;
img3->mmap = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (img3->mmap == (void*) -1)
{
perror("mmap");
free(img3);
return NULL;
}
keylen = 0;
if (config->iv != NULL && config->key != NULL)
{
if(strlen(config->iv) != 32)
{
printf("IV must be 16 bytes\n");
free(img3);
return NULL;
}
keylen = strlen(config->key);
if (keylen != 32 && keylen != 64 && keylen != 48)
{
printf("Key must be 16,24 or 32 bytes\n");
free(img3);
return NULL;
}
hexToBytes(config->iv, img3->iv, 16);
hexToBytes(config->key, img3->key, 32);
}
if(read(fd, &img3->header, sizeof(IMG3Header)) != sizeof(IMG3Header))
{
perror("read IMG3 header");
free(img3);
return NULL;
}
if(img3->header.magic != 'Img3' || img3->header.fullSize != len)
{
printf("bad magic or len\n");
free(img3);
return NULL;
}
for(offset=sizeof(IMG3Header),i=0; offset < len && i < MAX_IMG3_ELEMENTS; i++)
{
if(lseek(fd, offset, SEEK_SET) == -1)
break;
if(read(fd, &img3->elements[i], 12) != 12)
break;
if(img3->elements[i].total_length < 12)
break;
if(img3->elements[i].data_length > img3->elements[i].total_length)
break;
if(offset + img3->elements[i].data_length < offset)
break;
if(offset + img3->elements[i].total_length < offset)
break;
if(offset + img3->elements[i].total_length > len)
break;
img3->elements[i].offset = offset + 12;
img3->elements[i].name[0] = (img3->elements[i].magic & 0xff000000) >> 24;
img3->elements[i].name[1] = (img3->elements[i].magic & 0xff0000) >> 16;
img3->elements[i].name[2] = (img3->elements[i].magic & 0xff00) >> 8;
img3->elements[i].name[3] = (img3->elements[i].magic & 0xff);
img3->elements[i].name[4] = 0;
printf("TAG: %s OFFSET %x data_length:%x\n", img3->elements[i].name, offset, img3->elements[i].data_length);
if (img3->elements[i].magic == 'KBAG')
{
KBAG* kbag = (KBAG*) &(img3->mmap[offset+12]);
if(kbag->cryptState == 1)
{
if( kbag->aesType != 0x80 && kbag->aesType != 0xC0 && kbag->aesType != 0x100)
{
printf("Unknown aesType %x\n", kbag->aesType);
}
else if (keylen*4 != kbag->aesType)
{
printf("Wrong length for key parameter, got %d, aesType is %x\n", keylen, kbag->aesType);
free(img3);
return NULL;
}
else
{
printf("KBAG cryptState=%x aesType=%x\n", kbag->cryptState, kbag->aesType);
img3->aesType = kbag->aesType;
}
}
}
else if (img3->elements[i].magic == 'DATA')
{
img3->data_element = &img3->elements[i];
if (img3->header.iden == 'rdsk')
{
//XXX hdiutil fails if extension is not .dmg
strcpy(img3->elements[i].name, "DATA.dmg");
}
}
offset += img3->elements[i].total_length;
}
img3->num_elements = i;
if(img3->data_element != NULL && img3->aesType != 0)
{
img3->decrypted_data = malloc(img3->data_element->data_length);
if (img3->decrypted_data == NULL)
{
perror("FAIL: malloc(img3->data_element->data_length)");
free(img3);
return NULL;
}
switch(img3->aesType)
{
case 0x80:
img3->cipherType = EVP_aes_128_cbc();
break;
case 0xC0:
img3->cipherType = EVP_aes_192_cbc();
break;
case 0x100:
img3->cipherType = EVP_aes_256_cbc();
break;
default:
return img3; //should not reach
}
EVP_CIPHER_CTX ctx;
uint32_t decryptedLength = (img3->data_element->total_length - 12) & ~0xf;
printf("Decrypting DATA section\n");
EVP_CIPHER_CTX_init(&ctx);
EVP_DecryptInit_ex(&ctx, img3->cipherType, NULL, img3->key, img3->iv);
EVP_DecryptUpdate(&ctx, img3->decrypted_data, &decryptedLength,
&img3->mmap[img3->data_element->offset], decryptedLength);
char* info = img3_check_decrypted_data(img3->decrypted_data, decryptedLength);
if(info != NULL)
{
printf("Decrypted data seems OK : %s\n", info);
}
else
{
printf("Unknown decrypted data, key/iv might be wrong\n");
}
}
return img3;
}
static IMG3* img3 = NULL;
static void img3_destroy()
{
if (img3->aesType != 0 && img3->decrypted_data != NULL && img3->data_was_modified)
{
EVP_CIPHER_CTX ctx;
uint32_t encryptedLength = img3->data_element->total_length - 12;
//printf("Encrypting DATA section\n");
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit_ex(&ctx, img3->cipherType, NULL, img3->key, img3->iv);
EVP_EncryptUpdate(&ctx, &img3->mmap[img3->data_element->offset], &encryptedLength,
img3->decrypted_data, encryptedLength);
}
}
static int
img3_getattr(const char *path, struct stat *stbuf)
{
int i;
memset(stbuf, 0, sizeof(struct stat));
if(!strcmp(path, "/"))
{
stbuf->st_mode = S_IFDIR | 0777;
stbuf->st_nlink = 3;
return 0;
}
for(i=0; i < img3->num_elements; i++)
{
if(!strcmp(path+1, img3->elements[i].name))
{
stbuf->st_mode = S_IFREG | 0666;
stbuf->st_nlink = 1;
stbuf->st_size = img3->elements[i].data_length;
return 0;
}
}
return -ENOENT;
}
static int
img3_open(const char *path, struct fuse_file_info *fi)
{
int i;
for(i=0; i < img3->num_elements; i++)
{
if(!strcmp(path+1, img3->elements[i].name))
{
fi->fh = i;
return 0;
}
}
return -ENOENT;
}
static int
img3_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
int i;
if(strcmp(path, "/"))
{
return -ENOENT;
}
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
for(i=0; i < img3->num_elements; i++)
{
filler(buf, img3->elements[i].name, NULL, 0);
}
return 0;
}
static int
img3_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
IMG3Element* element = &img3->elements[fi->fh];
if (offset >= element->data_length) {
return 0;
}
if (offset + size > element->data_length) { /* Trim the read to the file size. */
size = element->data_length - offset;
}
if (img3->aesType != 0 && element == img3->data_element)
{
memcpy(buf, img3->decrypted_data + offset, size);
return size;
}
lseek(img3->fd, element->offset + offset, SEEK_SET);
return read(img3->fd, buf, size);
}
static int
img3_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
IMG3Element* element = &img3->elements[fi->fh];
if (offset >= element->data_length) {
return 0;
}
if (offset + size > element->data_length) { /* Trim the write to the file size. */
size = element->data_length - offset;
}
if (img3->aesType != 0 && element == img3->data_element)
{
img3->data_was_modified = 1;
memcpy(img3->decrypted_data + offset, buf, size);
return size;
}
lseek(img3->fd, element->offset + offset, SEEK_SET);
return write(img3->fd, buf, size);
}
static struct fuse_operations img3_filesystem_operations = {
.getattr = img3_getattr,
.open = img3_open,
.read = img3_read,
.write = img3_write,
.readdir = img3_readdir,
.destroy = img3_destroy
};
static int img3_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs)
{
static int i = -1;
struct img3_config* config = (struct img3_config*) data;
i++;
if (key == FUSE_OPT_KEY_NONOPT && i == 1)
{
config->img3filename = strdup(arg);
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct img3_config commandline_conf;
memset(&commandline_conf, 0, sizeof(commandline_conf));
fuse_opt_parse(&args, &commandline_conf, img3_opts, img3_opt_proc);
if (commandline_conf.img3filename == NULL)
{
printf("Usage %s mountpoint img3filename [-key KEY -iv IV]\n", argv[0]);
return -1;
}
img3 = img3_init(&commandline_conf);
if (img3 != NULL)
{
return fuse_main(args.argc, args.argv, &img3_filesystem_operations, img3_opt_proc);
}
return 0;
}