summaryrefslogtreecommitdiff
path: root/view
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2025-09-30 15:09:48 -0400
committerBrian Potchik <brian@vector35.com>2025-09-30 15:09:48 -0400
commit796932b62e7d007a46d8ad31cdb618e8125073ab (patch)
tree35937ae27a646cc6a1d7e79cafd4b46b0991a0b1 /view
parent36a06931b3eac492eb1e609949e2cc5a3a03bf57 (diff)
Initial support for opening container formats.
Diffstat (limited to 'view')
-rw-r--r--view/kernelcache/core/KernelCacheView.cpp149
-rw-r--r--view/kernelcache/core/transformers/KernelCacheTransforms.cpp210
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;
+ }
}
};