internal FFU(string Path) { this.Path = Path; try { OpenFile(); // Read Security Header byte[] ShortSecurityHeader = new byte[0x20]; FFUFile.Read(ShortSecurityHeader, 0, 0x20); if (ByteOperations.ReadAsciiString(ShortSecurityHeader, 0x04, 0x0C) != "SignedImage ") { throw new BadImageFormatException(); } ChunkSize = ByteOperations.ReadInt32(ShortSecurityHeader, 0x10) * 1024; UInt32 SecurityHeaderSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x00); UInt32 CatalogSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x18); UInt32 HashTableSize = ByteOperations.ReadUInt32(ShortSecurityHeader, 0x1C); SecurityHeader = new byte[RoundUpToChunks(SecurityHeaderSize + CatalogSize + HashTableSize)]; FFUFile.Seek(0, SeekOrigin.Begin); FFUFile.Read(SecurityHeader, 0, SecurityHeader.Length); // Read Image Header byte[] ShortImageHeader = new byte[0x1C]; FFUFile.Read(ShortImageHeader, 0, 0x1C); if (ByteOperations.ReadAsciiString(ShortImageHeader, 0x04, 0x0C) != "ImageFlash ") { throw new BadImageFormatException(); } UInt32 ImageHeaderSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x00); UInt32 ManifestSize = ByteOperations.ReadUInt32(ShortImageHeader, 0x10); ImageHeader = new byte[RoundUpToChunks(ImageHeaderSize + ManifestSize)]; FFUFile.Seek(SecurityHeader.Length, SeekOrigin.Begin); FFUFile.Read(ImageHeader, 0, ImageHeader.Length); // Read Store Header byte[] ShortStoreHeader = new byte[248]; FFUFile.Read(ShortStoreHeader, 0, 248); PlatformID = ByteOperations.ReadAsciiString(ShortStoreHeader, 0x0C, 192).TrimEnd(new char[] { (char)0, ' ' }); int WriteDescriptorCount = ByteOperations.ReadInt32(ShortStoreHeader, 208); UInt32 WriteDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 212); UInt32 ValidateDescriptorLength = ByteOperations.ReadUInt32(ShortStoreHeader, 220); StoreHeader = new byte[RoundUpToChunks(248 + WriteDescriptorLength + ValidateDescriptorLength)]; FFUFile.Seek(SecurityHeader.Length + ImageHeader.Length, SeekOrigin.Begin); FFUFile.Read(StoreHeader, 0, StoreHeader.Length); // Parse Chunk Indexes int HighestChunkIndex = 0; UInt32 LocationCount; int ChunkIndex; int ChunkCount; int DiskAccessMethod; UInt32 WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; int FFUChunkIndex = 0; for (int i = 0; i < WriteDescriptorCount; i++) { LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); for (int j = 0; j < LocationCount; j++) { DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); if (DiskAccessMethod == 0 && (ChunkIndex + ChunkCount - 1) > HighestChunkIndex) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. { HighestChunkIndex = ChunkIndex + ChunkCount - 1; } } WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); FFUChunkIndex += ChunkCount; } ChunkIndexes = new int?[HighestChunkIndex + 1]; WriteDescriptorEntryOffset = 248 + ValidateDescriptorLength; FFUChunkIndex = 0; for (int i = 0; i < WriteDescriptorCount; i++) { LocationCount = ByteOperations.ReadUInt32(StoreHeader, WriteDescriptorEntryOffset + 0x00); ChunkCount = ByteOperations.ReadInt32(StoreHeader, WriteDescriptorEntryOffset + 0x04); for (int j = 0; j < LocationCount; j++) { DiskAccessMethod = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x08 + (j * 0x08))); ChunkIndex = ByteOperations.ReadInt32(StoreHeader, (UInt32)(WriteDescriptorEntryOffset + 0x0C + (j * 0x08))); if (DiskAccessMethod == 0) // 0 = From begin, 2 = From end. We ignore chunks at end of disk. These contain secondairy GPT. { for (int k = 0; k < ChunkCount; k++) { ChunkIndexes[ChunkIndex + k] = FFUChunkIndex + k; } } } WriteDescriptorEntryOffset += 8 + (LocationCount * 0x08); FFUChunkIndex += ChunkCount; } byte[] GPTBuffer = GetSectors(0x01, 0x21); GPT = new GPT(GPTBuffer); HeaderSize = (UInt64)(SecurityHeader.Length + ImageHeader.Length + StoreHeader.Length); TotalChunkCount = (UInt64)FFUChunkIndex; PayloadSize = TotalChunkCount * (UInt64)ChunkSize; TotalSize = HeaderSize + PayloadSize; if (TotalSize != (UInt64)FFUFile.Length) { throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + "Expected size: " + TotalSize.ToString() + ". Actual size: " + FFUFile.Length + "."); } } catch (WPinternalsException) { throw; } catch (Exception Ex) { throw new WPinternalsException("Bad FFU file", "Bad FFU file: " + Path + "." + Environment.NewLine + Ex.Message, Ex); } finally { CloseFile(); } }
internal UEFI(byte[] UefiBinary) { Binary = UefiBinary; string VolumeHeaderMagic; UInt32?Offset = ByteOperations.FindAscii(Binary, "_FVH"); if (Offset == null) { throw new BadImageFormatException(); } else { VolumeHeaderOffset = (UInt32)Offset - 0x28; } if (!VerifyVolumeChecksum(Binary, VolumeHeaderOffset)) { throw new BadImageFormatException(); } VolumeSize = ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD VolumeHeaderSize = ByteOperations.ReadUInt16(Binary, VolumeHeaderOffset + 0x30); PaddingByteValue = (ByteOperations.ReadUInt32(Binary, VolumeHeaderOffset + 0x2C) & 0x00000800) > 0 ? (byte)0xFF : (byte)0x00; // EFI_FVB_ERASE_POLARITY = 0x00000800 // In the volume look for a file of type EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE (0x0B) FileHeaderOffset = VolumeHeaderOffset + VolumeHeaderSize; bool VolumeFound = false; int FileType; UInt32 FileSize; do { if (!VerifyFileChecksum(Binary, FileHeaderOffset)) { throw new BadImageFormatException(); } FileType = ByteOperations.ReadUInt8(Binary, FileHeaderOffset + 0x12); FileSize = ByteOperations.ReadUInt24(Binary, FileHeaderOffset + 0x14); if (FileType == 0x0B) // EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE { VolumeFound = true; } else { FileHeaderOffset += FileSize; // FileHeaderOffset in Volume-body must be Align 8 // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, // so that alignment can be ignored. FileHeaderOffset = ByteOperations.Align(VolumeHeaderOffset + VolumeHeaderSize, FileHeaderOffset, 8); } }while (!VolumeFound && (FileHeaderOffset < (VolumeHeaderOffset + VolumeSize))); if (!VolumeFound) { throw new BadImageFormatException(); } // Look in file for section of type EFI_SECTION_GUID_DEFINED (0x02) SectionHeaderOffset = FileHeaderOffset + 0x18; int SectionType; UInt32 SectionSize; UInt16 SectionHeaderSize = 0; bool DecompressedVolumeFound = false; do { SectionType = ByteOperations.ReadUInt8(Binary, SectionHeaderOffset + 0x03); SectionSize = ByteOperations.ReadUInt24(Binary, SectionHeaderOffset + 0x00); if (SectionType == 0x02) // EFI_SECTION_GUID_DEFINED { SectionHeaderSize = ByteOperations.ReadUInt16(Binary, SectionHeaderOffset + 0x14); DecompressedVolumeFound = true; } else { SectionHeaderOffset += SectionSize; // SectionHeaderOffset in File-body must be Align 4 SectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, SectionHeaderOffset, 4); } }while (!DecompressedVolumeFound && (SectionHeaderOffset < (FileHeaderOffset + FileSize))); if (!DecompressedVolumeFound) { throw new BadImageFormatException(); } // Decompress subvolume CompressedSubImageOffset = SectionHeaderOffset + SectionHeaderSize; CompressedSubImageSize = SectionSize - SectionHeaderSize; // DECOMPRESS HERE DecompressedImage = LZMA.Decompress(Binary, CompressedSubImageOffset, CompressedSubImageSize); // Extracted volume contains Sections at its root level DecompressedVolumeSectionHeaderOffset = 0; DecompressedVolumeFound = false; do { SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x03); SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x00); SectionHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeSectionHeaderOffset + 0x14); if (SectionType == 0x17) // EFI_SECTION_FIRMWARE_VOLUME_IMAGE { DecompressedVolumeFound = true; } else { DecompressedVolumeSectionHeaderOffset += SectionSize; // SectionHeaderOffset in File-body must be Align 4 DecompressedVolumeSectionHeaderOffset = ByteOperations.Align(FileHeaderOffset + 0x18, DecompressedVolumeSectionHeaderOffset, 4); } }while (!DecompressedVolumeFound && (DecompressedVolumeSectionHeaderOffset < DecompressedImage.Length)); if (!DecompressedVolumeFound) { throw new BadImageFormatException(); } DecompressedVolumeHeaderOffset = DecompressedVolumeSectionHeaderOffset + 4; // PARSE COMPRESSED VOLUME VolumeHeaderMagic = ByteOperations.ReadAsciiString(DecompressedImage, DecompressedVolumeHeaderOffset + 0x28, 0x04); if (VolumeHeaderMagic != "_FVH") { throw new BadImageFormatException(); } if (!VerifyVolumeChecksum(DecompressedImage, DecompressedVolumeHeaderOffset)) { throw new BadImageFormatException(); } Int32 DecompressedVolumeSize = ByteOperations.ReadInt32(DecompressedImage, DecompressedVolumeHeaderOffset + 0x20); // TODO: This is actually a QWORD UInt16 DecompressedVolumeHeaderSize = ByteOperations.ReadUInt16(DecompressedImage, DecompressedVolumeHeaderOffset + 0x30); // The files in this decompressed volume are the real EFI's. UInt32 DecompressedFileHeaderOffset = DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize; EFI CurrentEFI; do { if ((DecompressedFileHeaderOffset + 0x18) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) { break; } bool ContentFound = false; for (int i = 0; i < 0x18; i++) { if (DecompressedImage[DecompressedFileHeaderOffset + i] != PaddingByteValue) { ContentFound = true; break; } } if (!ContentFound) { break; } FileSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedFileHeaderOffset + 0x14); if ((DecompressedFileHeaderOffset + FileSize) >= (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)) { break; } if (!VerifyFileChecksum(DecompressedImage, DecompressedFileHeaderOffset)) { throw new BadImageFormatException(); } CurrentEFI = new EFI(); CurrentEFI.Type = ByteOperations.ReadUInt8(DecompressedImage, DecompressedFileHeaderOffset + 0x12); byte[] FileGuidBytes = new byte[0x10]; System.Buffer.BlockCopy(DecompressedImage, (int)DecompressedFileHeaderOffset + 0x00, FileGuidBytes, 0, 0x10); CurrentEFI.Guid = new Guid(FileGuidBytes); // Parse sections of the EFI CurrentEFI.FileOffset = DecompressedFileHeaderOffset; UInt32 DecompressedSectionHeaderOffset = DecompressedFileHeaderOffset + 0x18; do { SectionType = ByteOperations.ReadUInt8(DecompressedImage, DecompressedSectionHeaderOffset + 0x03); SectionSize = ByteOperations.ReadUInt24(DecompressedImage, DecompressedSectionHeaderOffset + 0x00); // SectionTypes that are relevant here: // 0x10 = PE File // 0x19 = RAW // 0x15 = Description // Not all section headers in the UEFI specs are 4 bytes long, // but the sections that are used in Windows Phone EFI's all have a header of 4 bytes. if (SectionType == 0x15) { CurrentEFI.Name = ByteOperations.ReadUnicodeString(DecompressedImage, DecompressedSectionHeaderOffset + 0x04, SectionSize - 0x04).TrimEnd(new char[] { (char)0, ' ' }); } else if ((SectionType == 0x10) || (SectionType == 0x19)) { CurrentEFI.SectionOffset = DecompressedSectionHeaderOffset; CurrentEFI.BinaryOffset = DecompressedSectionHeaderOffset + 0x04; CurrentEFI.Size = SectionSize - 0x04; } DecompressedSectionHeaderOffset += SectionSize; // SectionHeaderOffset in File-body must be Align 4 DecompressedSectionHeaderOffset = ByteOperations.Align(DecompressedFileHeaderOffset + 0x18, DecompressedSectionHeaderOffset, 4); }while (DecompressedSectionHeaderOffset < (DecompressedFileHeaderOffset + FileSize)); DecompressedFileHeaderOffset += FileSize; // FileHeaderOffset in Volume-body must be Align 8 // In the file-header-attributes the file-alignment relative to the start of the volume is always set to 1, // so that alignment can be ignored. DecompressedFileHeaderOffset = ByteOperations.Align(DecompressedVolumeHeaderOffset + DecompressedVolumeHeaderSize, DecompressedFileHeaderOffset, 8); EFIs.Add(CurrentEFI); }while (DecompressedFileHeaderOffset < (DecompressedVolumeHeaderOffset + DecompressedVolumeSize)); }