initial code for dumping imessages in a reasonable format
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
link_directories(${PROJECT_BINARY_DIR}/common ${PROJECT_BINARY_DIR}/hfs)
|
||||
|
||||
#set(COREFOUNDATION_LIBRARY CoreFoundation)
|
||||
|
||||
IF (APPLE)
|
||||
FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation)
|
||||
ENDIF (APPLE)
|
||||
|
||||
add_executable(emf_decrypter emf_decrypter.c emf_init.c)
|
||||
target_link_libraries (emf_decrypter hfs common crypto ${COREFOUNDATION_LIBRARY})
|
||||
|
||||
install(TARGETS emf_decrypter DESTINATION .)
|
||||
42
dump-imessages/iphone-dataprotection/emf_decrypter/emf/emf.h
Normal file
42
dump-imessages/iphone-dataprotection/emf_decrypter/emf/emf.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//As of iOS 4, class keys 1 to 4 are used for files, class 5 usage is unknown
|
||||
#define MAX_CLASS_KEYS 5
|
||||
#define CLASS_DKEY 4
|
||||
|
||||
typedef struct EMFInfo
|
||||
{
|
||||
Volume* volume;
|
||||
uint64_t volume_id;
|
||||
uint64_t volume_offset;
|
||||
uint32_t classKeys_bitset;
|
||||
AES_KEY emfkey;
|
||||
AES_KEY classKeys[MAX_CLASS_KEYS];
|
||||
}EMFInfo;
|
||||
|
||||
EMFInfo* EMF_init(Volume*, char*);
|
||||
|
||||
#define CPROTECT_V2_LENGTH 0x38 //56
|
||||
#define CP_WRAPPEDKEYSIZE 40 /* 2x4 = 8, 8x8 = 64 */
|
||||
|
||||
//http://www.opensource.apple.com/source/xnu/xnu-1699.22.73/bsd/sys/cprotect.h
|
||||
typedef struct cprotect_xattr_v2
|
||||
{
|
||||
uint16_t xattr_major_version; // =2
|
||||
uint16_t xattr_minor_version; // =0
|
||||
uint32_t flags; // leaks stack dword in one code path (cp_handle_vnop)
|
||||
uint32_t persistent_class;
|
||||
uint32_t key_size; //0x28
|
||||
uint8_t persistent_key[0x28];
|
||||
} cprotect_xattr_v2;
|
||||
|
||||
#define CPROTECT_V4_LENGTH 0x4C //76
|
||||
|
||||
typedef struct cprotect_xattr_v4
|
||||
{
|
||||
uint16_t xattr_major_version; // =4
|
||||
uint16_t xattr_minor_version; // =0
|
||||
uint32_t xxx_length; // 0xc
|
||||
uint32_t protection_class_id;
|
||||
uint32_t wrapped_length; //0x28
|
||||
uint8_t xxx_junk[20]; //uninitialized ?
|
||||
uint8_t wrapped_key[0x28];
|
||||
} cprotect_xattr_v4;
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <hfs/hfslib.h>
|
||||
#include "emf.h"
|
||||
|
||||
char endianness;
|
||||
|
||||
void TestByteOrder()
|
||||
{
|
||||
short int word = 0x0001;
|
||||
char *byte = (char *) &word;
|
||||
endianness = byte[0] ? IS_LITTLE_ENDIAN : IS_BIG_ENDIAN;
|
||||
}
|
||||
|
||||
void iv_for_lba(uint32_t lba, uint32_t* iv)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i < 4; i++)
|
||||
{
|
||||
if(lba & 1)
|
||||
lba = 0x80000061 ^ (lba >> 1);
|
||||
else
|
||||
lba = lba >> 1;
|
||||
iv[i] = lba;
|
||||
}
|
||||
}
|
||||
|
||||
int EMF_unwrap_filekey_forclass(EMFInfo* emf, uint8_t* wrapped_file_key, uint32_t protection_class_id, AES_KEY* file_key)
|
||||
{
|
||||
uint8_t fk[32]={0};
|
||||
|
||||
if (protection_class_id < 1 || protection_class_id >= MAX_CLASS_KEYS)
|
||||
return -1;
|
||||
|
||||
if ((emf->classKeys_bitset & (1 << protection_class_id)) == 0)
|
||||
{
|
||||
printf("Class key %d not available\n", protection_class_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(AES_unwrap_key(&(emf->classKeys[protection_class_id-1]), NULL, fk, wrapped_file_key, 40)!= 32)
|
||||
{
|
||||
fprintf(stderr, "EMF_unwrap_filekey_forclass unwrap FAIL, protection_class_id=%d\n", protection_class_id);
|
||||
return -1;
|
||||
}
|
||||
AES_set_decrypt_key(fk, 32*8, file_key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EMF_fix_and_decrypt_block(EMFInfo* emf, uint8_t* buffer, uint32_t lba, uint32_t blockSize, AES_KEY* filekey)
|
||||
{
|
||||
uint32_t volumeOffset = emf->volume_offset;
|
||||
uint32_t iv[4];
|
||||
|
||||
//reencrypt with emf key to get correct ciphertext
|
||||
iv_for_lba(volumeOffset + lba, iv);
|
||||
AES_cbc_encrypt(buffer, buffer, blockSize, &(emf->emfkey), (uint8_t*) iv, AES_ENCRYPT);
|
||||
|
||||
//decrypt with file key
|
||||
iv_for_lba(volumeOffset + lba, iv);
|
||||
AES_cbc_encrypt(buffer, buffer, blockSize, filekey, (uint8_t*) iv, AES_DECRYPT);
|
||||
}
|
||||
|
||||
int EMF_decrypt_file_blocks(EMFInfo* emf, HFSPlusCatalogFile* file, uint8_t* wrapped_file_key, uint32_t protection_class)
|
||||
{
|
||||
AES_KEY filekey;
|
||||
|
||||
if( EMF_unwrap_filekey_forclass(emf, wrapped_file_key, protection_class, &filekey))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
io_func* io = openRawFile(file->fileID, &file->dataFork, (HFSPlusCatalogRecord*)file, emf->volume);
|
||||
if(io == NULL)
|
||||
{
|
||||
fprintf(stderr, "openRawFile %d FAIL!\n", file->fileID);
|
||||
return -1;
|
||||
}
|
||||
RawFile* rawFile = (RawFile*) io->data;
|
||||
Extent* extent = rawFile->extents;
|
||||
uint32_t blockSize = emf->volume->volumeHeader->blockSize;
|
||||
uint32_t i;
|
||||
uint8_t* buffer = malloc(blockSize);
|
||||
|
||||
if(buffer == NULL)
|
||||
return -1;
|
||||
|
||||
//decrypt all blocks in all extents
|
||||
//the last block can contain stuff from erased files maybe ?
|
||||
while( extent != NULL)
|
||||
{
|
||||
for(i=0; i < extent->blockCount; i++)
|
||||
{
|
||||
if(READ(emf->volume->image, (extent->startBlock + i) * blockSize, blockSize, buffer))
|
||||
{
|
||||
EMF_fix_and_decrypt_block(emf, buffer, extent->startBlock + i, blockSize, &filekey);
|
||||
|
||||
//write back to image
|
||||
WRITE(emf->volume->image, (extent->startBlock + i) * blockSize, blockSize, buffer);
|
||||
}
|
||||
}
|
||||
extent = extent->next;
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EMF_decrypt_folder(EMFInfo* emf, HFSCatalogNodeID folderID)
|
||||
{
|
||||
CatalogRecordList* list;
|
||||
CatalogRecordList* theList;
|
||||
HFSPlusCatalogFolder* folder;
|
||||
HFSPlusCatalogFile* file;
|
||||
char* name;
|
||||
cprotect_xattr_v2* cprotect_xattr;
|
||||
uint8_t* wrapped_file_key;
|
||||
|
||||
theList = list = getFolderContents(folderID, emf->volume);
|
||||
|
||||
while(list != NULL)
|
||||
{
|
||||
name = unicodeToAscii(&list->name);
|
||||
|
||||
if(list->record->recordType == kHFSPlusFolderRecord)
|
||||
{
|
||||
folder = (HFSPlusCatalogFolder*)list->record;
|
||||
EMF_decrypt_folder(emf, folder->folderID);
|
||||
}
|
||||
else if(list->record->recordType == kHFSPlusFileRecord)
|
||||
{
|
||||
file = (HFSPlusCatalogFile*)list->record;
|
||||
|
||||
size_t attr_len = getAttribute(emf->volume, file->fileID, "com.apple.system.cprotect", (uint8_t**) &cprotect_xattr);
|
||||
|
||||
if(cprotect_xattr != NULL && attr_len > 0)
|
||||
{
|
||||
if (cprotect_xattr->xattr_major_version == 2 && attr_len == CPROTECT_V2_LENGTH)
|
||||
{
|
||||
printf("Decrypting %s\n", name);
|
||||
if(!EMF_decrypt_file_blocks(emf, file, cprotect_xattr->persistent_key, cprotect_xattr->persistent_class))
|
||||
{
|
||||
//TODO HAX: update cprotect xattr version field (bit1) to mark file as decrypted ?
|
||||
//cprotect_xattr->version |= 1;
|
||||
//setAttribute(volume, file->fileID, "com.apple.system.cprotect", (uint8_t*) cprotect_xattr, CPROTECT_V2_LENGTH);
|
||||
}
|
||||
}
|
||||
else if (cprotect_xattr->xattr_major_version == 4 && attr_len == CPROTECT_V4_LENGTH)
|
||||
{
|
||||
//not just yet :)
|
||||
}
|
||||
else if (cprotect_xattr->xattr_major_version & 1)
|
||||
{
|
||||
//TODO: file already decrypted by this tool ?
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unknown cprotect xattr version/length : %x/%zx\n", cprotect_xattr->xattr_major_version, attr_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(name);
|
||||
list = list->next;
|
||||
}
|
||||
releaseCatalogRecordList(theList);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
io_func* io;
|
||||
Volume* volume;
|
||||
|
||||
TestByteOrder();
|
||||
|
||||
if(argc < 2) {
|
||||
printf("usage: %s <image-file>\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
io = openFlatFile(argv[1]);
|
||||
|
||||
if(io == NULL) {
|
||||
fprintf(stderr, "error: Cannot open image-file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
volume = openVolume(io);
|
||||
if(volume == NULL) {
|
||||
fprintf(stderr, "error: Cannot open volume.\n");
|
||||
CLOSE(io);
|
||||
return 1;
|
||||
}
|
||||
printf("WARNING ! This tool will modify the hfs image and possibly wreck it if something goes wrong !\n"
|
||||
"Make sure to backup the image before proceeding\n");
|
||||
printf("Press a key to continue or CTRL-C to abort\n");
|
||||
getchar();
|
||||
|
||||
char* dir = dirname((char*)argv[1]);
|
||||
EMFInfo* emf = EMF_init(volume, dir);
|
||||
|
||||
if(emf != NULL)
|
||||
{
|
||||
EMF_decrypt_folder(emf, kHFSRootFolderID);
|
||||
}
|
||||
|
||||
closeVolume(volume);
|
||||
CLOSE(io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#include <stdio.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <hfs/hfsplus.h>
|
||||
#include <openssl/aes.h>
|
||||
#include "hfs/hfslib.h"
|
||||
#include "emf.h"
|
||||
|
||||
size_t ConvertHexaCFString(CFStringRef s, uint8_t** bytes)
|
||||
{
|
||||
uint32_t len = CFStringGetLength(s);
|
||||
uint8_t* hex = malloc(len+1);
|
||||
|
||||
if(hex == NULL)
|
||||
return 0;
|
||||
|
||||
if(!CFStringGetCString(s, hex, len+1, kCFStringEncodingASCII))
|
||||
{
|
||||
free(hex);
|
||||
return 0;
|
||||
}
|
||||
size_t size = 0;
|
||||
hexToBytes(hex, bytes, &size);
|
||||
free(hex);
|
||||
return size;
|
||||
}
|
||||
|
||||
void grabClassKey(const void *key, const void *value, void *context)
|
||||
{
|
||||
EMFInfo* emf = (EMFInfo*) context;
|
||||
uint8_t* class_key = NULL;
|
||||
|
||||
if(CFGetTypeID(key) != CFStringGetTypeID() || CFGetTypeID(value) != CFStringGetTypeID())
|
||||
return;
|
||||
|
||||
SInt32 clas = CFStringGetIntValue((CFStringRef)key);
|
||||
|
||||
if(clas > 0 && clas <= MAX_CLASS_KEYS && CFStringGetLength((CFStringRef) value) == 64)
|
||||
{
|
||||
if(ConvertHexaCFString(value, &class_key) == 32)
|
||||
{
|
||||
AES_set_decrypt_key(class_key, 32*8, &(emf->classKeys[clas-1]));
|
||||
free(class_key);
|
||||
emf->classKeys_bitset |= 1 << clas;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EMFInfo* EMF_init(Volume* volume, char* imagePath)
|
||||
{
|
||||
uint8_t* emfk = NULL;
|
||||
uint8_t* dkey = NULL;
|
||||
|
||||
uint64_t volume_id = *((uint64_t*) (&volume->volumeHeader->finderInfo[6]));
|
||||
FLIPENDIAN(volume_id);
|
||||
|
||||
if(imagePath == NULL)
|
||||
imagePath = ".";
|
||||
|
||||
printf("Volume identifier : %llx\n", volume_id);
|
||||
printf("Searching for %s/%llx.plist\n", imagePath, volume_id);
|
||||
|
||||
CFStringRef path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%llx.plist"), imagePath, volume_id);
|
||||
|
||||
CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, FALSE);
|
||||
CFRelease(path);
|
||||
|
||||
CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, fileURL);
|
||||
CFRelease(fileURL);
|
||||
|
||||
if(stream == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if(!CFReadStreamOpen(stream))
|
||||
{
|
||||
fprintf(stderr, "Cannot open file\n");
|
||||
return NULL;
|
||||
}
|
||||
CFPropertyListRef dict = CFPropertyListCreateWithStream(NULL, stream, 0, kCFPropertyListImmutable, NULL, NULL);
|
||||
|
||||
CFRelease(stream);
|
||||
|
||||
if (dict == NULL || CFGetTypeID(dict) != CFDictionaryGetTypeID())
|
||||
return NULL;
|
||||
|
||||
CFStringRef emfHex = CFDictionaryGetValue(dict, CFSTR("EMF"));
|
||||
CFStringRef dkeyHex = CFDictionaryGetValue(dict, CFSTR("DKey"));
|
||||
CFNumberRef dataVolumeOffset = CFDictionaryGetValue (dict, CFSTR("dataVolumeOffset"));
|
||||
|
||||
if (emfHex == NULL || CFGetTypeID(emfHex) != CFStringGetTypeID())
|
||||
return NULL;
|
||||
if (dkeyHex == NULL || CFGetTypeID(dkeyHex) != CFStringGetTypeID())
|
||||
return NULL;
|
||||
if (dataVolumeOffset == NULL || CFGetTypeID(dataVolumeOffset) != CFNumberGetTypeID())
|
||||
return NULL;
|
||||
|
||||
EMFInfo* emf = malloc(sizeof(EMFInfo));
|
||||
|
||||
if(emf == NULL)
|
||||
return NULL;
|
||||
|
||||
memset(emf, 0, sizeof(EMFInfo));
|
||||
|
||||
emf->volume = volume;
|
||||
|
||||
CFNumberGetValue(dataVolumeOffset, kCFNumberLongType, &emf->volume_offset);
|
||||
|
||||
printf("Data partition offset = %llx\n", emf->volume_offset);
|
||||
|
||||
if(ConvertHexaCFString(emfHex, &emfk) != 32)
|
||||
{
|
||||
fprintf(stderr, "Invalid EMF key\n");
|
||||
free(emf);
|
||||
return NULL;
|
||||
}
|
||||
if(ConvertHexaCFString(dkeyHex, &dkey) != 32)
|
||||
{
|
||||
fprintf(stderr, "Invalid DKey key\n");
|
||||
free(emf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AES_set_encrypt_key(emfk, 32*8, &(emf->emfkey));
|
||||
AES_set_decrypt_key(dkey, 32*8, &(emf->classKeys[CLASS_DKEY-1]));
|
||||
emf->classKeys_bitset |= 1 << CLASS_DKEY;
|
||||
|
||||
CFDictionaryRef classKeys = CFDictionaryGetValue(dict, CFSTR("classKeys"));
|
||||
|
||||
if(classKeys != NULL && CFGetTypeID(classKeys) == CFDictionaryGetTypeID())
|
||||
{
|
||||
printf("Reading class keys, NSProtectionComplete files should be decrypted OK\n");
|
||||
CFDictionaryApplyFunction(classKeys, grabClassKey, (void*) emf);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Only NSProtectionNone files will be decrypted\n");
|
||||
}
|
||||
|
||||
free(emfk);
|
||||
free(dkey);
|
||||
return emf;
|
||||
}
|
||||
Reference in New Issue
Block a user