diff options
| author | Brian Potchik <brian@vector35.com> | 2025-09-30 15:09:48 -0400 |
|---|---|---|
| committer | Brian Potchik <brian@vector35.com> | 2025-09-30 15:09:48 -0400 |
| commit | 796932b62e7d007a46d8ad31cdb618e8125073ab (patch) | |
| tree | 35937ae27a646cc6a1d7e79cafd4b46b0991a0b1 /view | |
| parent | 36a06931b3eac492eb1e609949e2cc5a3a03bf57 (diff) | |
Initial support for opening container formats.
Diffstat (limited to 'view')
| -rw-r--r-- | view/kernelcache/core/KernelCacheView.cpp | 149 | ||||
| -rw-r--r-- | view/kernelcache/core/transformers/KernelCacheTransforms.cpp | 210 |
2 files changed, 204 insertions, 155 deletions
diff --git a/view/kernelcache/core/KernelCacheView.cpp b/view/kernelcache/core/KernelCacheView.cpp index 03e8bd68..3535eec9 100644 --- a/view/kernelcache/core/KernelCacheView.cpp +++ b/view/kernelcache/core/KernelCacheView.cpp @@ -26,87 +26,15 @@ void KernelCacheViewType::Register() Ref<BinaryView> KernelCacheViewType::Create(BinaryView* data) { - uint32_t magic; - data->Read(&magic, data->GetStart(), 4); - if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit + try { - uint32_t im4pMagic; - data->Read(&im4pMagic, data->GetStart() + 0x8, 4); - if (im4pMagic == 0x50344d49) // P4MI - { - auto img4 = Transform::GetByName("IMG4-Unencrypted"); - - DataBuffer img4Payload; - img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload); - - DataBuffer machOPayload; - uint32_t magic = ((uint32_t*)img4Payload.GetData())[0]; - if (magic == FAT_MAGIC_64 || magic == MH_MAGIC_64 || magic == MH_MAGIC - || magic == MH_CIGAM_64 || magic == MH_CIGAM ) - { - machOPayload = img4Payload; - } - else if (strncmp((char*)img4Payload.GetData(), "bvx2", 4) == 0) - { - auto lzfse = Transform::GetByName("LZFSE"); - if (lzfse) - lzfse->Decode(img4Payload, machOPayload); - } - else - { -#ifdef COMPRESSION_DEBUG - LogError("Unknown compression type in IMG4 Payload, writing img4 payload to RAW view for debug purposes."); - LogError("KernelCache parsing will now fail to proceed."); - data->WriteBuffer(0, img4Payload); - return new KernelCacheView(KC_VIEW_NAME, data, false); -#else - LogError("Unknown compression type in IMG4 Payload, unable to proceed."); - LogError("You can manually extract the kernelcache using `kerneldec`,`ipsw`, or other tools."); - return nullptr; -#endif - } - - if (machOPayload.GetLength() == 0) - { -#ifdef COMPRESSION_DEBUG - LogError("Failed to perform extraction on IMG4 Payload, writing img4 payload to RAW view for debug purposes."); - LogError("KernelCache parsing will now fail to proceed."); - data->WriteBuffer(0, img4Payload); - return new KernelCacheView(KC_VIEW_NAME, data, false); -#else - return nullptr; -#endif - } - - uint32_t machoMagic = ((uint32_t*)machOPayload.GetData())[0]; - if (machoMagic == FAT_MAGIC_64) - { - DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c); - data->WriteBuffer(0, output); - } - else if (machoMagic == MH_MAGIC_64 || machoMagic == MH_MAGIC || machoMagic == MH_CIGAM_64 || machoMagic == MH_CIGAM) - { - data->WriteBuffer(0, machOPayload); - } - else - { -#ifdef COMPRESSION_DEBUG - LogError("Unknown Mach-O magic in IMG4 Payload, writing img4 payload to RAW view for debug purposes."); - LogError("KernelCache parsing will now fail to proceed."); - data->WriteBuffer(0, machOPayload); - return new KernelCacheView(KC_VIEW_NAME, data, false); -#else - return nullptr; -#endif - } - - return new KernelCacheView(KC_VIEW_NAME, data, false); - } - + return new KernelCacheView(KC_VIEW_NAME, data, false); + } + catch (std::exception& e) + { + LogErrorForException(e, "%s<BinaryViewType> failed to create view! '%s'", GetName().c_str(), e.what()); return nullptr; } - - return new KernelCacheView(KC_VIEW_NAME, data, false); } Ref<Settings> KernelCacheViewType::GetLoadSettingsForData(BinaryView* data) @@ -159,41 +87,15 @@ Ref<Settings> KernelCacheViewType::GetLoadSettingsForData(BinaryView* data) Ref<BinaryView> KernelCacheViewType::Parse(BinaryView* data) { - uint32_t magic; - data->Read(&magic, data->GetStart(), 4); - if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit + try { - uint32_t im4pMagic; - data->Read(&im4pMagic, data->GetStart() + 0x8, 4); - if (im4pMagic == 0x50344d49) // P4MI - { - auto img4 = Transform::GetByName("IMG4-Unencrypted"); - auto lzfse = Transform::GetByName("LZFSE"); - - DataBuffer img4Payload; - img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload); - DataBuffer machOPayload; - lzfse->Decode(img4Payload, machOPayload); - - uint32_t magic = ((uint32_t*)machOPayload.GetData())[0]; - auto id = data->BeginUndoActions(); - if (magic == FAT_MAGIC_64) - { - DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c); - data->WriteBuffer(0, output); - } - else - { - data->WriteBuffer(0, machOPayload); - } - data->ForgetUndoActions(id); - return new KernelCacheView(KC_VIEW_NAME, data, true); - } - + return new KernelCacheView(KC_VIEW_NAME, data, true); + } + catch (std::exception& e) + { + LogErrorForException(e, "%s<BinaryViewType> failed to create view! '%s'", GetName().c_str(), e.what()); return nullptr; } - - return new KernelCacheView(KC_VIEW_NAME, data, true); } bool KernelCacheViewType::IsTypeValidForData(BinaryView* data) @@ -204,36 +106,15 @@ bool KernelCacheViewType::IsTypeValidForData(BinaryView* data) uint32_t magic; data->Read(&magic, data->GetStart(), 4); - if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit - { - uint32_t im4pMagic; - data->Read(&im4pMagic, data->GetStart() + 0x8, 4); - if (im4pMagic == 0x50344d49) // P4MI - { - auto img4 = Transform::GetByName("IMG4-Unencrypted"); - auto lzfse = Transform::GetByName("LZFSE"); - - DataBuffer img4Payload; - img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload); - DataBuffer machOPayload; - lzfse->Decode(img4Payload, machOPayload); - - uint32_t magic = ((uint32_t*)machOPayload.GetData())[0]; - if (magic == FAT_MAGIC_64 || magic == MH_CIGAM_64 || magic == MH_MAGIC_64) - return true; - - return false; - } - + // TODO determine if the "krnl" string of the IMG4 container is required for non-database files + // If so, inspect the metadata from the TransformSession that produced this BinaryView + if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) return false; - } uint32_t fileType; data->Read(&fileType, data->GetStart() + 0xc, 4); if (fileType != MH_FILESET) - { return false; - } return true; } diff --git a/view/kernelcache/core/transformers/KernelCacheTransforms.cpp b/view/kernelcache/core/transformers/KernelCacheTransforms.cpp index 5cecabad..c2da5cdb 100644 --- a/view/kernelcache/core/transformers/KernelCacheTransforms.cpp +++ b/view/kernelcache/core/transformers/KernelCacheTransforms.cpp @@ -15,12 +15,11 @@ class IMG4PayloadTransform : public Transform { public: - IMG4PayloadTransform(): - Transform(DecodeTransform, "IMG4-Unencrypted", "IMG4-Unencrypted", "IMG4") + IMG4PayloadTransform(): Transform(DecodeTransform, TransformSupportsDetection, "IMG4", "IMG4", "Container") { } - virtual bool Decode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>& params) + virtual bool Decode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>& params) override { DERItem* item = new DERItem; item->data = (DERByte *)input.GetData(); @@ -29,46 +28,215 @@ public: Img4Payload *payload = new Img4Payload; DERImg4DecodePayload(item, payload); + if (!payload->payload.data || !payload->payload.length) + return false; + output = DataBuffer(payload->payload.data, payload->payload.length); return true; } + + static void der_put_len(std::vector<uint8_t>& v, size_t len) { + if (len < 0x80) { v.push_back(static_cast<uint8_t>(len)); return; } + uint8_t tmp[9]; size_t n = 0; + while (len) { tmp[n++] = static_cast<uint8_t>(len & 0xFF); len >>= 8; } + v.push_back(static_cast<uint8_t>(0x80 | n)); + for (size_t i = 0; i < n; ++i) v.push_back(tmp[n - 1 - i]); + } + + static void der_put_ia5(std::vector<uint8_t>& v, const void* s, size_t len) { + v.push_back(0x16); // IA5String + der_put_len(v, len); + const uint8_t* p = static_cast<const uint8_t*>(s); + v.insert(v.end(), p, p + len); + } + + // TODO fix/support round-tripping of optional fields (type, desc) + virtual bool Encode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>& params) override + { + // type (exactly 4 chars) + const char* type = "krnl"; + if (auto it = params.find("type"); it != params.end()) { + if (it->second.GetLength() != 4) return false; + type = reinterpret_cast<const char*>(it->second.GetData()); + } + // optional desc (IA5String) + const char* desc = nullptr; size_t descLen = 0; + if (auto it = params.find("desc"); it != params.end() && it->second.GetLength() > 0) { + desc = reinterpret_cast<const char*>(it->second.GetData()); + descLen = it->second.GetLength(); + } + + // Build SEQUENCE content + std::vector<uint8_t> body; + der_put_ia5(body, "IM4P", 4); // magic + der_put_ia5(body, type, 4); // type + if (desc && descLen) der_put_ia5(body, desc, descLen); // optional + + // payload as [1] EXPLICIT OCTET STRING + // std::vector<uint8_t> os; + // os.push_back(0x04); // OCTET STRING + // der_put_len(os, input.GetLength()); + // os.insert(os.end(), + // static_cast<const uint8_t*>(input.GetData()), + // static_cast<const uint8_t*>(input.GetData()) + input.GetLength()); + + // body.push_back(0xA1); // [1] EXPLICIT + // der_put_len(body, os.size()); + // body.insert(body.end(), os.begin(), os.end()); + + // --- payload as *bare* OCTET STRING (what DERImg4DecodePayload expects) --- + body.push_back(0x04); // OCTET STRING + der_put_len(body, input.GetLength()); + body.insert(body.end(), + static_cast<const uint8_t*>(input.GetData()), + static_cast<const uint8_t*>(input.GetData()) + input.GetLength()); + + + // Wrap in SEQUENCE + std::vector<uint8_t> out; + out.push_back(0x30); // SEQUENCE + der_put_len(out, body.size()); + out.insert(out.end(), body.begin(), body.end()); + + output = DataBuffer(out.data(), out.size()); // copies + return true; + } + + virtual bool CanDecode(Ref<BinaryView> input) const override + { + uint8_t header[64]; + size_t bytesRead = input->Read(header, 0, sizeof(header)); + if (bytesRead < sizeof(header)) + return false; + + const uint8_t* data = header; + size_t headerLength = bytesRead; + size_t inputLength = input->GetLength(); + + auto parseDerLen = [](const uint8_t* ptr, size_t available) -> std::pair<size_t, size_t> { + if (!available) + return {0, 0}; + + uint8_t firstByte = ptr[0]; + if (firstByte < 0x80) // Short form + return {firstByte, 1}; + if (firstByte == 0x80) // Invalid indefinite length + return {0, 0}; + + size_t lengthBytes = firstByte & 0x7F; + if (lengthBytes == 0 || lengthBytes > sizeof(size_t) || lengthBytes >= available || ptr[1] == 0x00) + return {0, 0}; + + size_t result = 0; + for (size_t i = 0; i < lengthBytes; ++i) + result = (result << 8) | ptr[1 + i]; + return {result, 1 + lengthBytes}; + }; + + if (headerLength < 8) // Minimum: SEQUENCE tag(1) + len(1) + IA5String tag(1) + len(1) + "IM4P"(4) + return false; + + if (data[0] != 0x30) // Check for DER sequence start + return false; + + auto [seqLen, seqLenHdr] = parseDerLen(data + 1, headerLength - 1); + if (!seqLen || !seqLenHdr || ((seqLen + 1 + seqLenHdr) > inputLength)) + return false; + + size_t offset = 1 + seqLenHdr; + size_t seqEnd = offset + seqLen; + + if (seqLen > (inputLength - offset)) + return false; + + // parse up to the first 5 elements to find the magic "IM4P" + for (int i = 0; i < 5 && offset < seqEnd; ++i) + { + if (seqEnd - offset < 2) + return false; + uint8_t tag = data[offset++]; + auto [elementLen, elementLenHdr] = parseDerLen(data + offset, seqEnd - offset); + if (!elementLen || !elementLenHdr || (elementLen > (seqEnd - offset - elementLenHdr))) + return false; + offset += elementLenHdr; + if ((tag == 0x16) && (elementLen == 4) && memcmp(data + offset, "IM4P", 4) == 0) + return true; + } + + return false; + } }; class LZFSETransform : public Transform { public: - LZFSETransform(): Transform(BinaryCodecTransform, "LZFSE", "LZFSE", "Compress") + LZFSETransform(): Transform(BinaryCodecTransform, TransformSupportsDetection, "LZFSE", "LZFSE", "Compress") { } - virtual bool Decode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>& params) + virtual bool Decode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>& params) override { size_t outputBufferSize = input.GetLength() * 6; - std::unique_ptr<uint8_t[]> lzfseOutputBuffer(new uint8_t[outputBufferSize]); std::unique_ptr<uint8_t[]> scratchBuffer(new uint8_t[lzfse_decode_scratch_size()]); - size_t outSize = lzfse_decode_buffer(lzfseOutputBuffer.get(), outputBufferSize, - (uint8_t *)input.GetData(), input.GetLength(), - scratchBuffer.get()); - if (!outSize) - return false; - output = DataBuffer(lzfseOutputBuffer.get(), outSize); - return true; + while (true) + { + output.SetSize(outputBufferSize); + size_t outSize = lzfse_decode_buffer((uint8_t *)output.GetData(), outputBufferSize, (uint8_t *)input.GetData(), input.GetLength(), scratchBuffer.get()); + if (!outSize) + return false; + if ((outSize > 0) && (outSize < outputBufferSize)) + { + output.SetSize(outSize); + return true; + } + if (output.GetLength() > (size_t(1) << 33)) // 8GB max + return false; + outputBufferSize *= 2; + } + + return false; } - virtual bool Encode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>&) + virtual bool Encode(const DataBuffer& input, DataBuffer& output, const std::map<std::string, DataBuffer>&) override { size_t outputBufferSize = input.GetLength() + (input.GetLength() / 16) + 64; - std::unique_ptr<uint8_t[]> lzfseOutputBuffer(new uint8_t[outputBufferSize]); std::unique_ptr<uint8_t[]> scratchBuffer(new uint8_t[lzfse_encode_scratch_size()]); - size_t outSize = lzfse_encode_buffer(lzfseOutputBuffer.get(), outputBufferSize, - (uint8_t *)input.GetData(), input.GetLength(), - scratchBuffer.get()); - if (!outSize) + for (int attempts = 0; attempts < 10; attempts++) + { + output.SetSize(outputBufferSize); + size_t outSize = lzfse_encode_buffer((uint8_t *)output.GetData(), outputBufferSize, (uint8_t *)input.GetData(), input.GetLength(), scratchBuffer.get()); + if (outSize > 0) + { + output.SetSize(outSize); + return true; + } + outputBufferSize *= 2; + } + + return false; + } + + virtual bool CanDecode(Ref<BinaryView> input) const override + { + uint8_t header[4]; + if (input->Read(header, 0, 4) < 4) return false; - output = DataBuffer(lzfseOutputBuffer.get(), outSize); - return true; + + if (header[0] != 0x62 || header[1] != 0x76 || header[2] != 0x78) // Check for "bvx" prefix (common to all LZFSE blocks) + return false; + + switch (header[3]) + { + case '-': // raw + case '1': // compressed v1 + case '2': // compressed v2 + case 'n': // LZVN + return true; + default: + return false; + } } }; |
