Files
WPinternals/ViewModels/LumiaV3FlashRomViewModel.cs
T
Gustave Monce c5fcb1ec8d Implement Qualcomm Sahara VIP and fix a few bugs
* Qualcomm Sahara VIP
* Project Cleanup
* Allow unlocking an already unlocked phone
2021-08-11 14:33:49 +02:00

506 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace WPinternals
{
internal class LumiaV3FlashRomViewModel : ContextViewModel
{
private static void RoundUpToChunks(Stream stream, UInt32 chunkSize)
{
Int64 Size = stream.Length;
if ((Size % chunkSize) > 0)
{
Int64 padding = (UInt32)(((Size / chunkSize) + 1) * chunkSize) - Size;
stream.Write(new byte[padding], 0, (Int32)padding);
}
}
internal class ImageHeader
{
public UInt32 Size = 24;
public string Signature = "ImageFlash ";
public UInt32 ManifestLength;
public UInt32 ChunkSize = 128;
}
public class StoreHeader
{
public UInt32 UpdateType = 0; // Full update
public UInt16 MajorVersion = 1;
public UInt16 MinorVersion = 0;
public UInt16 FullFlashMajorVersion = 2;
public UInt16 FullFlashMinorVersion = 0;
// Size is 0xC0
public string PlatformId;
public UInt32 BlockSizeInBytes = 131072;
public UInt32 WriteDescriptorCount;
public UInt32 WriteDescriptorLength;
public UInt32 ValidateDescriptorCount = 0;
public UInt32 ValidateDescriptorLength = 0;
public UInt32 InitialTableIndex = 0;
public UInt32 InitialTableCount = 0;
public UInt32 FlashOnlyTableIndex = 0; // Should be the index of the critical partitions, but for now we don't implement that
public UInt32 FlashOnlyTableCount = 1;
public UInt32 FinalTableIndex; //= WriteDescriptorCount - FinalTableCount;
public UInt32 FinalTableCount = 0;
}
public class SecurityHeader
{
public UInt32 Size = 32;
public string Signature = "SignedImage ";
public UInt32 ChunkSizeInKb = 128;
public UInt32 HashAlgorithm = 32780; // SHA256 algorithm id
public UInt32 CatalogSize;
public UInt32 HashTableSize;
}
internal static class ManifestIni
{
internal static string BuildUpManifest(FullFlash fullFlash, Store store)
{
string Manifest = "[FullFlash]\r\n";
if (!string.IsNullOrEmpty(fullFlash.AntiTheftVersion))
{
Manifest += "AntiTheftVersion = " + fullFlash.AntiTheftVersion + "\r\n";
}
if (!string.IsNullOrEmpty(fullFlash.OSVersion))
{
Manifest += "OSVersion = " + fullFlash.OSVersion + "\r\n";
}
if (!string.IsNullOrEmpty(fullFlash.Description))
{
Manifest += "Description = " + fullFlash.Description + "\r\n";
}
if (!string.IsNullOrEmpty(fullFlash.Version))
{
Manifest += "Version = " + fullFlash.Version + "\r\n";
}
if (!string.IsNullOrEmpty(fullFlash.DevicePlatformId0))
{
Manifest += "DevicePlatformId0 = " + fullFlash.DevicePlatformId0 + "\r\n";
}
Manifest += "\r\n[Store]\r\n";
Manifest += "SectorSize = " + store.SectorSize + "\r\n";
Manifest += "MinSectorCount = " + store.MinSectorCount + "\r\n";
Manifest += "\r\n";
return Manifest;
}
}
internal class FullFlash
{
public string AntiTheftVersion = "1.1"; // Allow flashing on all devices
public string OSVersion;
public string Description = "Update on: " + DateTime.Now.ToString("u") + "::\r\n";
public string Version = "2.0";
public string DevicePlatformId0;
}
internal class Store
{
public UInt32 SectorSize;
public UInt32 MinSectorCount;
}
// V3 exploit
// Magic
//
private static byte[] GenerateCatalogFile(byte[] hashData)
{
byte[] catalog_first_part = new byte[] { 0x30, 0x82, 0x01, 0x44, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02, 0xA0, 0x82, 0x01, 0x35, 0x30, 0x82, 0x01, 0x31, 0x02, 0x01, 0x01, 0x31, 0x00, 0x30, 0x82, 0x01, 0x26, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0A, 0x01, 0xA0, 0x82, 0x01, 0x17, 0x30, 0x82, 0x01, 0x13, 0x30, 0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0C, 0x01, 0x01, 0x04, 0x10, 0xA8, 0xCA, 0xD9, 0x7D, 0xBF, 0x6D, 0x67, 0x4D, 0xB1, 0x4D, 0x62, 0xFB, 0xE6, 0x26, 0x22, 0xD4, 0x17, 0x0D, 0x32, 0x30, 0x30, 0x31, 0x31, 0x30, 0x31, 0x32, 0x31, 0x32, 0x32, 0x37, 0x5A, 0x30, 0x0E, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0C, 0x01, 0x02, 0x05, 0x00, 0x30, 0x81, 0xD1, 0x30, 0x81, 0xCE, 0x04, 0x1E, 0x48, 0x00, 0x61, 0x00, 0x73, 0x00, 0x68, 0x00, 0x54, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x2E, 0x00, 0x62, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x62, 0x00, 0x00, 0x00, 0x31, 0x81, 0xAB, 0x30, 0x45, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04, 0x31, 0x37, 0x30, 0x35, 0x30, 0x10, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x19, 0xA2, 0x02, 0x80, 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 };
byte[] catalog_second_part = new byte[] { 0x30, 0x62, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0C, 0x02, 0x02, 0x31, 0x54, 0x30, 0x52, 0x1E, 0x4C, 0x00, 0x7B, 0x00, 0x44, 0x00, 0x45, 0x00, 0x33, 0x00, 0x35, 0x00, 0x31, 0x00, 0x41, 0x00, 0x34, 0x00, 0x32, 0x00, 0x2D, 0x00, 0x38, 0x00, 0x45, 0x00, 0x35, 0x00, 0x39, 0x00, 0x2D, 0x00, 0x31, 0x00, 0x31, 0x00, 0x44, 0x00, 0x30, 0x00, 0x2D, 0x00, 0x38, 0x00, 0x43, 0x00, 0x34, 0x00, 0x37, 0x00, 0x2D, 0x00, 0x30, 0x00, 0x30, 0x00, 0x43, 0x00, 0x30, 0x00, 0x34, 0x00, 0x46, 0x00, 0x43, 0x00, 0x32, 0x00, 0x39, 0x00, 0x35, 0x00, 0x45, 0x00, 0x45, 0x00, 0x7D, 0x02, 0x02, 0x02, 0x00, 0x31, 0x00 };
byte[] hash = new SHA1Managed().ComputeHash(hashData);
byte[] catalog = new byte[catalog_first_part.Length + hash.Length + catalog_second_part.Length];
Buffer.BlockCopy(catalog_first_part, 0, catalog, 0, catalog_first_part.Length);
Buffer.BlockCopy(hash, 0, catalog, catalog_first_part.Length, hash.Length);
Buffer.BlockCopy(catalog_second_part, 0, catalog, catalog_first_part.Length + hash.Length, catalog_second_part.Length);
return catalog;
}
// V3 exploit
// Magic
//
internal async static Task LumiaV3CustomFlash(PhoneNotifierViewModel Notifier, List<FlashPart> FlashParts, bool CheckSectorAlignment = true, SetWorkingStatus SetWorkingStatus = null, UpdateWorkingStatus UpdateWorkingStatus = null, ExitSuccess ExitSuccess = null, ExitFailure ExitFailure = null)
{
if (SetWorkingStatus == null)
{
SetWorkingStatus = (m, s, v, a, st) => { };
}
if (UpdateWorkingStatus == null)
{
UpdateWorkingStatus = (m, s, v, st) => { };
}
if (ExitSuccess == null)
{
ExitSuccess = (m, s) => { };
}
if (ExitFailure == null)
{
ExitFailure = (m, s) => { };
}
const uint chunkSize = 131072u;
const int chunkSizes = 131072;
if (FlashParts != null)
{
foreach (FlashPart Part in FlashParts)
{
if (Part.Stream == null)
{
throw new ArgumentException("Stream is null");
}
if (!Part.Stream.CanSeek)
{
throw new ArgumentException("Streams must be seekable");
}
if ((Part.StartSector * 0x200 % chunkSize) != 0)
{
throw new ArgumentException("Invalid StartSector alignment");
}
if (CheckSectorAlignment && (Part.Stream.Length % chunkSize) != 0)
{
throw new ArgumentException("Invalid Data length");
}
}
}
try
{
NokiaFlashModel Model = (NokiaFlashModel)Notifier.CurrentModel;
PhoneInfo Info = Model.ReadPhoneInfo();
if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV2)) == 0) // Exploit needs protocol v2 -> This check is not conclusive, because old phones also report support for this protocol, although it is really not supported.
{
throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that it does not support the Protocol Sync V2.");
}
if (Info.FlashAppProtocolVersionMajor < 2) // Old phones do not support the hack. These phones have Flash protocol 1.x.
{
throw new WPinternalsException("Flash failed!", "Protocols not supported. The phone reports that Flash App communication protocol is lower than 2. Reported version by the phone: " + Info.FlashAppProtocolVersionMajor + ".");
}
if (Info.UefiSecureBootEnabled)
{
throw new WPinternalsException("Flash failed!", "UEFI Secureboot must be disabled for the Flash V3 exploit to work.");
}
// The payloads must be ordered by the number of locations
//
// FlashApp processes payloads like this:
// - First payloads which are with one location, those can be sent in bulk
// - Then payloads with more than one location, those should not be sent in bulk
//
// If you do not order payloads like this, you will get an error, most likely hash mismatch
//
LumiaV2UnlockBootViewModel.FlashingPayload[] payloads = Array.Empty<LumiaV2UnlockBootViewModel.FlashingPayload>();
if (FlashParts != null)
{
payloads = LumiaV2UnlockBootViewModel.GetNonOptimizedPayloads(FlashParts, chunkSizes, Info.WriteBufferSize / chunkSize, SetWorkingStatus, UpdateWorkingStatus).OrderBy(x => x.TargetLocations.Length).ToArray();
}
MemoryStream Headerstream1 = new();
// ==============================
// Header 1 start
ImageHeader image = new();
FullFlash ffimage = new();
Store simage = new();
// Todo make this read the image itself
ffimage.OSVersion = "10.0.11111.0";
ffimage.DevicePlatformId0 = Info.PlatformID;
ffimage.AntiTheftVersion = "1.1";
simage.SectorSize = 512;
simage.MinSectorCount = Info.EmmcSizeInSectors;
//Logging.Log("Generating image manifest...");
string manifest = ManifestIni.BuildUpManifest(ffimage, simage);//, partitions);
byte[] TextBytes = Encoding.ASCII.GetBytes(manifest);
image.ManifestLength = (UInt32)TextBytes.Length;
byte[] ImageHeaderBuffer = new byte[0x18];
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0, image.Size);
ByteOperations.WriteAsciiString(ImageHeaderBuffer, 0x04, image.Signature);
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x10, image.ManifestLength);
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x14, image.ChunkSize);
Headerstream1.Write(ImageHeaderBuffer, 0, 0x18);
Headerstream1.Write(TextBytes, 0, TextBytes.Length);
RoundUpToChunks(Headerstream1, chunkSize);
// Header 1 stop + round
// ==============================
MemoryStream Headerstream2 = new();
// ==============================
// Header 2 start
StoreHeader store = new();
store.WriteDescriptorCount = (UInt32)payloads.Length;
store.FinalTableIndex = (UInt32)payloads.Length - store.FinalTableCount;
store.PlatformId = Info.PlatformID;
foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
{
store.WriteDescriptorLength += payload.GetStoreHeaderSize();
}
byte[] GPTChunk = LumiaUnlockBootloaderViewModel.GetGptChunk(Model, 0x20000);
GPT GPT = new(GPTChunk);
UInt64 PlatEnd = 0;
if (GPT.Partitions.Any(x => x.Name == "PLAT"))
{
PlatEnd = GPT.GetPartition("PLAT").LastSector;
}
foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
{
if (payload.TargetLocations[0] > PlatEnd)
{
break;
}
store.FlashOnlyTableIndex++;
}
byte[] StoreHeaderBuffer = new byte[0xF8];
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0, store.UpdateType);
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x04, store.MajorVersion);
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x06, store.MinorVersion);
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x08, store.FullFlashMajorVersion);
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x0A, store.FullFlashMinorVersion);
ByteOperations.WriteAsciiString(StoreHeaderBuffer, 0x0C, store.PlatformId);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xCC, store.BlockSizeInBytes);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD0, store.WriteDescriptorCount);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD4, store.WriteDescriptorLength);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD8, store.ValidateDescriptorCount);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xDC, store.ValidateDescriptorLength);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE0, store.InitialTableIndex);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE4, store.InitialTableCount);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE8, store.FlashOnlyTableIndex);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xEC, store.FlashOnlyTableCount);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF0, store.FinalTableIndex);
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF4, store.FinalTableCount);
Headerstream2.Write(StoreHeaderBuffer, 0, 0xF8);
byte[] descriptorsBuffer = new byte[store.WriteDescriptorLength];
UInt32 NewWriteDescriptorOffset = 0;
foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
{
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, (UInt32)payload.TargetLocations.Length); // Location count
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, payload.ChunkCount); // Chunk count
NewWriteDescriptorOffset += 0x08;
foreach (UInt32 location in payload.TargetLocations)
{
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, 0x00000000); // Disk access method (0 = Begin, 2 = End)
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, location); // Chunk index
NewWriteDescriptorOffset += 0x08;
}
}
Headerstream2.Write(descriptorsBuffer, 0, (Int32)store.WriteDescriptorLength);
RoundUpToChunks(Headerstream2, chunkSize);
// Header 2 stop + round
// ==============================
SecurityHeader security = new();
Headerstream1.Seek(0, SeekOrigin.Begin);
Headerstream2.Seek(0, SeekOrigin.Begin);
security.HashTableSize = 0x20 * (UInt32)((Headerstream1.Length + Headerstream2.Length) / chunkSize);
foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
{
security.HashTableSize += payload.GetSecurityHeaderSize();
}
byte[] HashTable = new byte[security.HashTableSize];
BinaryWriter bw = new(new MemoryStream(HashTable));
SHA256 crypto = SHA256.Create();
for (Int32 i = 0; i < Headerstream1.Length / chunkSize; i++)
{
byte[] buffer = new byte[chunkSize];
Headerstream1.Read(buffer, 0, (Int32)chunkSize);
byte[] hash = crypto.ComputeHash(buffer);
bw.Write(hash, 0, hash.Length);
}
for (Int32 i = 0; i < Headerstream2.Length / chunkSize; i++)
{
byte[] buffer = new byte[chunkSize];
Headerstream2.Read(buffer, 0, (Int32)chunkSize);
byte[] hash = crypto.ComputeHash(buffer);
bw.Write(hash, 0, hash.Length);
}
foreach (LumiaV2UnlockBootViewModel.FlashingPayload payload in payloads)
{
bw.Write(payload.ChunkHashes[0], 0, payload.ChunkHashes[0].Length);
}
bw.Close();
//Logging.Log("Generating image catalog...");
byte[] catalog = GenerateCatalogFile(HashTable);
security.CatalogSize = (UInt32)catalog.Length;
byte[] SecurityHeaderBuffer = new byte[0x20];
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0, security.Size);
ByteOperations.WriteAsciiString(SecurityHeaderBuffer, 0x04, security.Signature);
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x10, security.ChunkSizeInKb);
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x14, security.HashAlgorithm);
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x18, security.CatalogSize);
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x1C, security.HashTableSize);
MemoryStream retstream = new();
retstream.Write(SecurityHeaderBuffer, 0, 0x20);
retstream.Write(catalog, 0, (Int32)security.CatalogSize);
retstream.Write(HashTable, 0, (Int32)security.HashTableSize);
RoundUpToChunks(retstream, chunkSize);
Headerstream1.Seek(0, SeekOrigin.Begin);
Headerstream2.Seek(0, SeekOrigin.Begin);
byte[] buff = new byte[Headerstream1.Length];
Headerstream1.Read(buff, 0, (Int32)Headerstream1.Length);
Headerstream1.Close();
retstream.Write(buff, 0, buff.Length);
buff = new byte[Headerstream2.Length];
Headerstream2.Read(buff, 0, (Int32)Headerstream2.Length);
Headerstream2.Close();
retstream.Write(buff, 0, buff.Length);
// --------
// Go back to the beginning
retstream.Seek(0, SeekOrigin.Begin);
byte[] FfuHeader = new byte[retstream.Length];
await retstream.ReadAsync(FfuHeader, 0, (Int32)retstream.Length);
retstream.Close();
Byte Options = 0;
if (!Info.IsBootloaderSecure)
{
Options = (Byte)((FlashOptions)Options | FlashOptions.SkipSignatureCheck);
}
LogFile.Log("Flash in progress...", LogType.ConsoleOnly);
SetWorkingStatus("Flashing...", null, (UInt64?)payloads.Length, Status: WPinternalsStatus.Flashing);
Model.SendFfuHeaderV1(FfuHeader, Options);
UInt64 counter = 0;
Int32 numberOfPayloadsToSendAtOnce = 1;
if ((Info.SecureFfuSupportedProtocolMask & (UInt16)FfuProtocol.ProtocolSyncV2) != 0)
{
numberOfPayloadsToSendAtOnce = (Int32)Math.Round((Double)Info.WriteBufferSize / chunkSize);
}
byte[] payloadBuffer;
for (Int32 i = 0; i < payloads.Length; i+= numberOfPayloadsToSendAtOnce)
{
if (i + numberOfPayloadsToSendAtOnce - 1 >= payloads.Length)
{
numberOfPayloadsToSendAtOnce = payloads.Length - i;
}
payloadBuffer = new byte[numberOfPayloadsToSendAtOnce * chunkSizes];
string ProgressText = "Flashing resources";
for (Int32 j = 0; j < numberOfPayloadsToSendAtOnce; j++)
{
LumiaV2UnlockBootViewModel.FlashingPayload payload = payloads[i + j];
UInt32 StreamIndex = payload.StreamIndexes[0];
FlashPart flashPart = FlashParts[(Int32)StreamIndex];
if (flashPart.ProgressText != null)
{
ProgressText = flashPart.ProgressText;
}
Stream Stream = flashPart.Stream;
Stream.Seek(payload.StreamLocations[0], SeekOrigin.Begin);
Stream.Read(payloadBuffer, j * chunkSizes, chunkSizes);
counter++;
}
if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) != 0)
{
Model.SendFfuPayloadV2(payloadBuffer, Int32.Parse((counter * 100 / (UInt64)payloads.Length).ToString()));
}
else
{
Model.SendFfuPayloadV1(payloadBuffer, Int32.Parse((counter * 100 / (UInt64)payloads.Length).ToString()));
}
UpdateWorkingStatus(ProgressText, null, counter, WPinternalsStatus.Flashing);
}
Model.ResetPhone();
await Notifier.WaitForRemoval();
ExitSuccess("Flash succeeded!", null);
}
catch
{
throw new WPinternalsException("Custom flash failed");
}
}
}
}