dmg/attribution.c (247 lines of code) (raw):

#include <string.h> #include <zlib.h> // For crc32_combine. #include "abstractfile.h" #include "common.h" #include <dmg/attribution.h> #include <dmg/dmg.h> typedef struct AttributionPreservingSentinelData { char* sentinel; ChecksumToken beforeRaw_UncompressedChkToken; ChecksumToken raw_UncompressedChkToken; ChecksumToken afterRaw_UncompressedChkToken; int64_t beforeRaw_UncompressedLength; // -1 if sentinel (raw block) not yet seen. int64_t raw_UncompressedLength; int64_t afterRaw_UncompressedLength; ChecksumToken beforeRaw_CompressedChkToken; ChecksumToken raw_CompressedChkToken; ChecksumToken afterRaw_CompressedChkToken; int64_t beforeRaw_CompressedLength; // -1 if sentinel (raw block) not yet seen. int64_t raw_CompressedLength; int64_t afterRaw_CompressedLength; } AttributionPreservingSentinelData; char const * FindStrInBuf(char const * buf, size_t bufLen, char const * str) { size_t index = 0; while (index < bufLen) { char const * result = strstr(buf + index, str); if (result) { return result; } while ((buf[index] != '\0') && (index < bufLen)) { index++; } index++; } return NULL; } enum ShouldKeepRaw sentinelShouldKeepRaw(AbstractAttribution* attribution, const void* data, size_t len, const void* nextData, size_t nextLen) { AttributionPreservingSentinelData* attributionData = (AttributionPreservingSentinelData*)attribution->data; if (NULL != FindStrInBuf((char const*)data, len, (const char*)attributionData->sentinel)) { return KeepCurrentRaw; } // If we didn't find it in the data, check if it spans data + nextData if (nextLen > 0) { char* combinedData = malloc(len + nextLen); memcpy(combinedData, data, len); memcpy(combinedData + len, nextData, nextLen); if (NULL != FindStrInBuf((char const*)combinedData, len + nextLen, (const char*)attributionData->sentinel)) { return KeepCurrentAndNextRaw; } } return KeepNoneRaw; } void sentinelBeforeMainBlkx(AbstractAttribution* attribution, AbstractFile* abstractOut, ChecksumToken* dataForkToken) { AttributionPreservingSentinelData* attributionData = (AttributionPreservingSentinelData*)attribution->data; memcpy(&attributionData->beforeRaw_CompressedChkToken, dataForkToken, sizeof(ChecksumToken)); attributionData->beforeRaw_CompressedLength = abstractOut->tell(abstractOut); } void sentinelObserveBuffers(AbstractAttribution* attribution, int didKeepRaw, const void* uncompressedData, size_t uncompressedLen, const void* compressedData, size_t compressedLen) { AttributionPreservingSentinelData* attributionData = (AttributionPreservingSentinelData*)attribution->data; if (didKeepRaw) { // If this is our first time in, we need to set up `raw_*` and `afterRaw*` // from scratch. if (attributionData->afterRaw_UncompressedLength < 0) { // Just in case. memset(&attributionData->raw_UncompressedChkToken, 0, sizeof(ChecksumToken)); memset(&attributionData->raw_CompressedChkToken, 0, sizeof(ChecksumToken)); CRCProxy(&attributionData->raw_UncompressedChkToken, uncompressedData, uncompressedLen); attributionData->raw_UncompressedLength = uncompressedLen; // printf("Adding to raw_UncompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->raw_UncompressedChkToken.crc, attributionData->raw_UncompressedLength); // Of course, the compressed data *should* be the uncompressed data. CRCProxy(&attributionData->raw_CompressedChkToken, compressedData, compressedLen); attributionData->raw_CompressedLength = compressedLen; // printf("Adding to raw_CompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->raw_CompressedChkToken.crc, attributionData->raw_CompressedLength); attributionData->afterRaw_UncompressedLength = 0; attributionData->afterRaw_CompressedLength = 0; // Just in case. memset(&attributionData->afterRaw_UncompressedChkToken, 0, sizeof(ChecksumToken)); memset(&attributionData->afterRaw_CompressedChkToken, 0, sizeof(ChecksumToken)); } // If this is our second time through, we just need to update the `raw*` values. else { CRCProxy(&attributionData->raw_UncompressedChkToken, uncompressedData, uncompressedLen); attributionData->raw_UncompressedLength += uncompressedLen; // printf("Appending to raw_UncompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->raw_UncompressedChkToken.crc, attributionData->raw_UncompressedLength); CRCProxy(&attributionData->raw_CompressedChkToken, compressedData, compressedLen); attributionData->raw_CompressedLength += compressedLen; // printf("Appending to raw_CompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->raw_CompressedChkToken.crc, attributionData->raw_CompressedLength); } } else { if (attributionData->afterRaw_UncompressedLength < 0) { CRCProxy(&attributionData->beforeRaw_UncompressedChkToken, uncompressedData, uncompressedLen); attributionData->beforeRaw_UncompressedLength += uncompressedLen; // printf("Adding to beforeRaw_UncompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->beforeRaw_UncompressedChkToken.crc, attributionData->beforeRaw_UncompressedLength); } else { CRCProxy(&attributionData->afterRaw_UncompressedChkToken, uncompressedData, uncompressedLen); attributionData->afterRaw_UncompressedLength += uncompressedLen; // printf("Adding to afterRaw_UncompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->afterRaw_UncompressedChkToken.crc, attributionData->afterRaw_UncompressedLength); } if (attributionData->afterRaw_CompressedLength < 0) { CRCProxy(&attributionData->beforeRaw_CompressedChkToken, compressedData, compressedLen); attributionData->beforeRaw_CompressedLength += compressedLen; // printf("Adding to beforeRaw_CompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->beforeRaw_CompressedChkToken.crc, attributionData->beforeRaw_CompressedLength); } else { CRCProxy(&attributionData->afterRaw_CompressedChkToken, compressedData, compressedLen); attributionData->afterRaw_CompressedLength += compressedLen; // printf("Adding to afterRaw_CompressedChkToken, now CRC32: %x, length: %llx\n", attributionData->afterRaw_CompressedChkToken.crc, attributionData->afterRaw_CompressedLength); } } } void sentinelAfterMainBlkx(AbstractAttribution* attribution, AbstractFile* abstractOut, ChecksumToken* dataForkToken, AttributionResource* attributionResource) { AttributionPreservingSentinelData* data = attribution->data; // Sentinels no longer make sense. if (data->raw_UncompressedLength < 0) { data->raw_UncompressedLength = 0; } if (data->afterRaw_UncompressedLength < 0) { data->afterRaw_UncompressedLength = 0; } if (data->raw_CompressedLength < 0) { data->raw_CompressedLength = 0; } if (data->afterRaw_CompressedLength < 0) { data->afterRaw_CompressedLength = 0; } printf("\n"); printf("data->beforeRaw_UncompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->beforeRaw_UncompressedChkToken.crc, data->beforeRaw_UncompressedLength); printf("data->raw_UncompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->raw_UncompressedChkToken.crc, data->raw_UncompressedLength); printf("data->afterRaw_UncompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->afterRaw_UncompressedChkToken.crc, data->afterRaw_UncompressedLength); printf("uncompressed combined CRC32: 0x%lx\n", crc32_combine(crc32_combine(data->beforeRaw_UncompressedChkToken.crc, data->raw_UncompressedChkToken.crc, data->raw_UncompressedLength), data->afterRaw_UncompressedChkToken.crc, data->afterRaw_UncompressedLength)); printf("\n"); printf("data->beforeRaw_CompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->beforeRaw_CompressedChkToken.crc, data->beforeRaw_CompressedLength); printf("data->raw_CompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->raw_CompressedChkToken.crc, data->raw_CompressedLength); printf("data->afterRaw_CompressedChkToken CRC32: 0x%x, length: 0x%llx\n", data->afterRaw_CompressedChkToken.crc, data->afterRaw_CompressedLength); printf("compressed combined CRC32: 0x%lx\n\n", crc32_combine(crc32_combine(data->beforeRaw_CompressedChkToken.crc, data->raw_CompressedChkToken.crc, data->raw_CompressedLength), data->afterRaw_CompressedChkToken.crc, data->afterRaw_CompressedLength)); // Update attribution metadata. attributionResource->signature = ATTR_SIGNATURE; attributionResource->version = 1; attributionResource->beforeCompressedLength = data->beforeRaw_CompressedLength; attributionResource->beforeCompressedChecksum = data->beforeRaw_CompressedChkToken.crc; attributionResource->beforeUncompressedLength = data->beforeRaw_UncompressedLength; attributionResource->beforeUncompressedChecksum = data->beforeRaw_UncompressedChkToken.crc; attributionResource->rawPos = data->beforeRaw_CompressedLength; attributionResource->rawLength = data->raw_CompressedLength; attributionResource->rawChecksum = data->raw_CompressedChkToken.crc; attributionResource->afterCompressedLength = data->afterRaw_CompressedLength; attributionResource->afterCompressedChecksum = data->afterRaw_CompressedChkToken.crc; attributionResource->afterUncompressedLength = data->afterRaw_UncompressedLength; attributionResource->afterUncompressedChecksum = data->afterRaw_UncompressedChkToken.crc; printf("sentinelAfterMainBlkx: %d, 0x%llx, 0x%llx\n", attributionResource->version, attributionResource->rawPos, attributionResource->rawLength); } AbstractAttribution* createAbstractAttributionPreservingSentinel(const char* sentinel) { AbstractAttribution* attribution; attribution = (AbstractAttribution*) malloc(sizeof(AbstractAttribution)); AttributionPreservingSentinelData* data = malloc(sizeof(AttributionPreservingSentinelData)); memset(data, 0, sizeof(AttributionPreservingSentinelData)); data->sentinel = malloc(strlen(sentinel) + 1); strcpy(data->sentinel, sentinel); data->raw_UncompressedLength = -1; data->afterRaw_UncompressedLength = -1; data->raw_CompressedLength = -1; data->afterRaw_CompressedLength = -1; attribution->data = data; attribution->beforeMainBlkx = sentinelBeforeMainBlkx; attribution->shouldKeepRaw = sentinelShouldKeepRaw; attribution->observeBuffers = sentinelObserveBuffers; attribution->afterMainBlkx = sentinelAfterMainBlkx; return attribution; } uint32_t calculateMasterChecksum(ResourceKey* resources); int updateAttribution(AbstractFile* abstractIn, AbstractFile* abstractOut, const char* anchor, const char* data, size_t dataLen) { // In an `attributable` DMG file: // - read `attribution` resource // - update bytes in BZ_RAW block in place // - update <blkx> checksum: there's a UDIF checksum (34 bytes?) in // each <blkx> dict, which is part of a Base64 encoded struct. We // can Base64 decode to bytes, swizzle the 4 bytes of the // checksum, and then Base64 encode back to the same number of // bytes. // - update data fork checksum (compressed) // - update master checksum (uncompressed) off_t fileLength; UDIFResourceFile resourceFile; ResourceData* curData; fileLength = abstractIn->getLength(abstractIn); abstractIn->seek(abstractIn, fileLength - sizeof(UDIFResourceFile)); readUDIFResourceFile(abstractIn, &resourceFile); char* resourceXML; resourceXML = malloc(resourceFile.fUDIFXMLLength + 1); ASSERT( abstractIn->seek(abstractIn, (off_t)(resourceFile.fUDIFXMLOffset)) == 0, "fseeko" ); ASSERT( abstractIn->read(abstractIn, resourceXML, (size_t)resourceFile.fUDIFXMLLength) == (size_t)resourceFile.fUDIFXMLLength, "fread" ); resourceXML[resourceFile.fUDIFXMLLength] = 0; ResourceKey* resources; resources = readResources(resourceXML, resourceFile.fUDIFXMLLength, true); ResourceKey* resource = getResourceByKey(resources, "plst"); unsigned char* mine = (unsigned char*)(resource->data->name); AttributionResource* attributionResource = (AttributionResource*)(resource->data->name); ASSERT(attributionResource->signature == ATTR_SIGNATURE, "bad attr signature!"); ASSERT(attributionResource->version == 1, "only version 1 recognized"); printf("attribution: at 0x%llx, 0x%llx bytes\n", attributionResource->rawPos, attributionResource->rawLength); // Step 1. Replace bytes at anchor. ASSERT(abstractIn->seek(abstractIn, 0) == 0, "seek in"); size_t inLength = abstractIn->getLength(abstractIn); while (1) { unsigned char buffer[8192]; size_t readLength = abstractIn->read(abstractIn, buffer, 8192); ASSERT(readLength == abstractOut->write(abstractOut, buffer, readLength), "write copy"); if (readLength < 8192) { break; } } ASSERT(abstractIn->seek(abstractIn, attributionResource->rawPos) == 0, "seek in"); char* rawBuffer = malloc(attributionResource->rawLength); ASSERT(rawBuffer, "malloc rawBuffer"); ASSERT(abstractIn->read(abstractIn, rawBuffer, attributionResource->rawLength) == attributionResource->rawLength, "read raw in"); printf("Looking for anchor: '%s'\n", anchor); const char* rawAnchor = FindStrInBuf((const char*)rawBuffer, attributionResource->rawLength, anchor); ASSERT(rawAnchor, "anchor position"); int64_t anchorOffset = rawAnchor - rawBuffer; printf("anchorOffset: 0x%llx\n", anchorOffset); ASSERT(rawAnchor + dataLen <= rawBuffer + attributionResource->rawLength, "data too long!"); // Zero out the anchor area, in case the data is shorter than the anchor // Note that we're taking the strlen of `rawAnchor` and not the `anchor` // passed in. This ensures that the entire anchor is zero'ed, even if only // a prefix of it was provided. memset((void *)rawAnchor, 0, strlen(rawAnchor)); // Copy the new data into the same spot the anchor was in memcpy((void *)rawAnchor, data, dataLen); // Write the new block. ASSERT(abstractOut->seek(abstractOut, attributionResource->rawPos) == 0, "seek out"); ASSERT(abstractOut->write(abstractOut, rawBuffer, attributionResource->rawLength) == attributionResource->rawLength, "write data"); // New block checksum. ChecksumToken newRawToken; memset(&newRawToken, 0, sizeof(ChecksumToken)); CRCProxy(&newRawToken, (unsigned char*)rawBuffer, attributionResource->rawLength); free(rawBuffer); // Step 2: update "attribution" resource. attributionResource->rawChecksum = newRawToken.crc; // Step 3. Update blkx checksum. // TODO: check ranges of all the partitions and runs to be sure we're identifying the correct file system. int partNum = -1; ResourceData* blkxData = NULL; if(partNum < 0) { blkxData = getResourceByKey(resources, "blkx")->data; while(blkxData != NULL) { if(strstr(blkxData->name, "Apple_HFS") != NULL) { break; } blkxData = blkxData->next; } } else { blkxData = getDataByID(getResourceByKey(resources, "blkx"), partNum); } ASSERT(blkxData, "blkxData"); BLKXTable* blkx = (BLKXTable*)(blkxData->data); blkx->checksum.data[0] = crc32_combine(crc32_combine(attributionResource->beforeUncompressedChecksum, newRawToken.crc, attributionResource->rawLength), attributionResource->afterUncompressedChecksum, attributionResource->afterUncompressedLength); ASSERT( abstractOut->seek(abstractOut, (off_t)(resourceFile.fUDIFXMLOffset)) == 0, "fseeko" ); writeResources(abstractOut, resources, true); // Step 4. Update koly block checksums. resourceFile.fUDIFDataForkChecksum.type = CHECKSUM_UDIF_CRC32; resourceFile.fUDIFDataForkChecksum.bitness = checksumBitness(CHECKSUM_UDIF_CRC32); resourceFile.fUDIFDataForkChecksum.data[0] = crc32_combine(crc32_combine(attributionResource->beforeCompressedChecksum, newRawToken.crc, attributionResource->rawLength), attributionResource->afterCompressedChecksum, attributionResource->afterCompressedLength); resourceFile.fUDIFMasterChecksum.type = CHECKSUM_UDIF_CRC32; resourceFile.fUDIFMasterChecksum.bitness = checksumBitness(CHECKSUM_UDIF_CRC32); resourceFile.fUDIFMasterChecksum.data[0] = calculateMasterChecksum(resources); printf("Master checksum: %x\n", resourceFile.fUDIFMasterChecksum.data[0]); fflush(stdout); printf("Writing out UDIF resource file...\n"); fflush(stdout); writeUDIFResourceFile(abstractOut, &resourceFile); printf("Cleaning up...\n"); fflush(stdout); releaseResources(resources); abstractIn->close(abstractIn); printf("Done\n"); fflush(stdout); abstractOut->close(abstractOut); return TRUE; }