Files
WPinternals/WPinternals/Models/Lumia/UEFI/Flash/LumiaFlashAppModel.cs
T
Gustave Monce bc77e8b589 Fixes
2024-10-21 08:56:11 +02:00

1232 lines
51 KiB
C#

// Copyright (c) 2018, Rene Lergner - @Heathcliff74xda
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using System.Linq;
using System.Text;
using WPinternals.HelperClasses;
using WPinternals.Models.Lumia.UEFI;
using WPinternals.Models.Lumia.UEFI.Flash;
using WPinternals.Terminal;
namespace WPinternals.Models.UEFIApps.Flash
{
internal class LumiaFlashAppModel : NokiaUEFIModel
{
internal readonly LumiaFlashAppPhoneInfo FlashAppInfo = new();
private UefiSecurityStatusResponse _SecurityStatus = null;
//
// Not valid commands
//
/* NOK */
private const string Signature = "NOK";
/* NOKX */
private const string ExtendedMessageSignature = $"{Signature}X";
/* NOKXF */
private const string LumiaFlashAppExtendedMessageSignature = $"{ExtendedMessageSignature}F";
//
// Normal commands
//
/* NOKB */ // TODO
private const string RPMBSignature = $"{Signature}B";
/* NOKD */
private const string DisableTimeoutsSignature = $"{Signature}D";
/* NOKE */
private const string EnableSecureFFUConfigSignature = $"{Signature}E";
/* NOKF */
private const string FlashSignature = $"{Signature}F";
/* NOKG */
private const string FactoryResetSignature = $"{Signature}G";
/* NOKI */
private const string HelloSignature = $"{Signature}I";
/* NOKJ */
private const string JTAGDisableSignature = $"{Signature}J";
/* NOKK */
private const string KickWatchdogSignature = $"{Signature}K";
/* NOKL */
private const string LoadFlashAppSignature = $"{Signature}L";
/* NOKM */
private const string RebootToMassStorageSignature = $"{Signature}M";
/* NOKN */
private const string AuthenticateSignature = $"{Signature}N";
/* NOKP */
private const string RebootToPhoneInfoAppSignature = $"{Signature}P";
/* NOKR */
private const string RebootSignature = $"{Signature}R";
/* NOKT */
private const string GetGPTSignature = $"{Signature}T";
/* NOKV */
private const string InfoQuerySignature = $"{Signature}V";
/* NOKZ */
private const string ShutdownSignature = $"{Signature}Z";
//
// Lumia Flash App extended commands
//
/* NOKXFB */
private const string BackupSignature = $"{LumiaFlashAppExtendedMessageSignature}B";
/* NOKXFC */
private const string CertificateSignature = $"{LumiaFlashAppExtendedMessageSignature}C";
/* NOKXFD */
private const string PlatformSecureBootEnableSignature = $"{LumiaFlashAppExtendedMessageSignature}D";
/* NOKXFE */
private const string FlashEraseSignature = $"{LumiaFlashAppExtendedMessageSignature}E";
/* NOKXFF */
private const string AsyncFlashModeSignature = $"{LumiaFlashAppExtendedMessageSignature}F";
/* NOKXFG */
private const string CreateGoldenBackupSignature = $"{LumiaFlashAppExtendedMessageSignature}G";
/* NOKXFH */
private const string WriteRootCertificateHashSignature = $"{LumiaFlashAppExtendedMessageSignature}H";
/* NOKXFK */
private const string UEFIKeysProvisionSignature = $"{LumiaFlashAppExtendedMessageSignature}K";
/* NOKXFL */
private const string LoadSignature = $"{LumiaFlashAppExtendedMessageSignature}L";
/* NOKXFP */
private const string FlashPartitionEraseSignature = $"{LumiaFlashAppExtendedMessageSignature}P";
/* NOKXFR */
private const string ReadParamSignature = $"{LumiaFlashAppExtendedMessageSignature}R";
/* NOKXFS */
private const string SecureFlashSignature = $"{LumiaFlashAppExtendedMessageSignature}S";
/* NOKXFT */
private const string TerminalChallengeSignature = $"{LumiaFlashAppExtendedMessageSignature}T";
/* NOKXFW */
private const string WriteParamSignature = $"{LumiaFlashAppExtendedMessageSignature}W";
/* NOKXFY */
private const string MMOSStartCommandSignature = $"{LumiaFlashAppExtendedMessageSignature}Y";
public LumiaFlashAppModel(string DevicePath) : base(DevicePath)
{
}
public void WriteParam(string Param, byte[] Value)
{
// Command: NOKXFW
// Padding (one byte)
// Parameter (4 bytes)
// Length (4 bytes)
// Data
if (Param.Length > 4)
{
throw new ArgumentOutOfRangeException(nameof(Param));
}
byte[] Request = new byte[15 + Param.Length];
const string Header = WriteParamSignature;
Buffer.BlockCopy(Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(Encoding.ASCII.GetBytes(Param), 0, Request, 7, Param.Length);
Buffer.BlockCopy(BigEndian.GetBytes(Value.Length, 4), 0, Request, 11, 4);
Buffer.BlockCopy(Value, 0, Request, 15, Value.Length);
byte[] Response = ExecuteRawMethod(Request);
if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU")
{
throw new NotSupportedException();
}
UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]);
if (Error > 0)
{
throw new NotSupportedException("WriteParam: Error 0x" + Error.ToString("X4"));
}
}
internal void ResetToMassStorageMode()
{
byte[] Request = new byte[4];
ByteOperations.WriteAsciiString(Request, 0, RebootToMassStorageSignature);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
// Succeeded
return;
}
if (ByteOperations.ReadAsciiString(Response, 0, 4) == "NOKU")
{
throw new NotSupportedException();
}
UInt16 Error = (UInt16)((Response[6] << 8) + Response[7]);
if (Error > 0)
{
throw new NotSupportedException("SendCommonExtendedSwitchToCommand: Error 0x" + Error.ToString("X4"));
}
}
internal LumiaFlashAppPhoneInfo ReadPhoneInfo(bool ExtendedInfo = true)
{
// NOKH = Get Phone Info (IMEI and info from Product.dat) - Not available on some phones, like Lumia 640.
// NOKV = Info Query
bool PhoneInfoLogged = FlashAppInfo.State != PhoneInfoState.Empty;
ReadPhoneInfoFlashApp();
LumiaFlashAppPhoneInfo Result = FlashAppInfo;
if (ExtendedInfo && Result.State == PhoneInfoState.Basic)
{
Result.Firmware = ReadStringParam("FVER");
Result.RKH = ReadParam("RRKH");
Result.State = PhoneInfoState.Extended;
}
if (!PhoneInfoLogged)
{
Result.Log(LogType.FileOnly);
}
return Result;
}
internal LumiaFlashAppPhoneInfo ReadPhoneInfoFlashApp()
{
// NOKH = Get Phone Info (IMEI and info from Product.dat) - Not available on some phones, like Lumia 640.
// NOKV = Info Query
LumiaFlashAppPhoneInfo Result = FlashAppInfo;
if (Result.State == PhoneInfoState.Empty)
{
byte[] Request = new byte[4];
ByteOperations.WriteAsciiString(Request, 0, InfoQuerySignature);
byte[] Response = ExecuteRawMethod(Request);
if (Response != null && ByteOperations.ReadAsciiString(Response, 0, 4) != "NOKU")
{
Result.App = (FlashAppType)Response[5];
switch (Result.App)
{
case FlashAppType.FlashApp:
Result.FlashAppProtocolVersionMajor = Response[6];
Result.FlashAppProtocolVersionMinor = Response[7];
Result.FlashAppVersionMajor = Response[8];
Result.FlashAppVersionMinor = Response[9];
break;
}
byte SubblockCount = Response[10];
int SubblockOffset = 11;
for (int i = 0; i < SubblockCount; i++)
{
byte SubblockID = Response[SubblockOffset + 0x00];
LogFile.Log($"{Result.App} SubblockID: 0x{SubblockID:X}");
ushort SubblockLength = BigEndian.ToUInt16(Response, SubblockOffset + 0x01);
int SubblockPayloadOffset = SubblockOffset + 3;
byte SubblockVersion;
switch (SubblockID)
{
case 0x01:
Result.TransferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset);
break;
case 0x02:
Result.WriteBufferSize = BigEndian.ToUInt32(Response, SubblockPayloadOffset);
break;
case 0x03:
Result.EmmcSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset);
break;
case 0x04:
Result.SdCardSizeInSectors = BigEndian.ToUInt32(Response, SubblockPayloadOffset);
break;
case 0x05:
Result.PlatformID = ByteOperations.ReadAsciiString(Response, (uint)SubblockPayloadOffset, SubblockLength).Trim([' ', '\0']);
break;
case 0x0D:
Result.AsyncSupport = Response[SubblockPayloadOffset + 1] == 1;
break;
case 0x0F:
SubblockVersion = Response[SubblockPayloadOffset]; // 0x03
Result.PlatformSecureBootEnabled = Response[SubblockPayloadOffset + 0x01] == 0x01;
Result.SecureFfuEnabled = Response[SubblockPayloadOffset + 0x02] == 0x01;
Result.JtagDisabled = Response[SubblockPayloadOffset + 0x03] == 0x01;
Result.RdcPresent = Response[SubblockPayloadOffset + 0x04] == 0x01;
Result.Authenticated = Response[SubblockPayloadOffset + 0x05] == 0x01 || Response[SubblockPayloadOffset + 0x05] == 0x02;
Result.UefiSecureBootEnabled = Response[SubblockPayloadOffset + 0x06] == 0x01;
Result.SecondaryHardwareKeyPresent = Response[SubblockPayloadOffset + 0x07] == 0x01;
break;
case 0x10:
SubblockVersion = Response[SubblockPayloadOffset]; // 0x01
Result.SecureFfuSupportedProtocolMask = BigEndian.ToUInt16(Response, SubblockPayloadOffset + 0x01);
break;
case 0x1F:
Result.MmosOverUsbSupported = Response[SubblockPayloadOffset] == 1;
break;
case 0x20:
// CRC header info
break;
}
SubblockOffset += SubblockLength + 3;
}
}
Result.State = PhoneInfoState.Basic;
}
Result.IsBootloaderSecure = !(FlashAppInfo.Authenticated || FlashAppInfo.RdcPresent || !FlashAppInfo.SecureFfuEnabled);
return Result;
}
internal bool CanReadGPT()
{
LumiaFlashAppPhoneInfo Info = ReadPhoneInfo(ExtendedInfo: false);
FlashAppType OriginalAppType = Info.App;
return Info.IsBootloaderSecure;
}
internal GPT ReadGPT()
{
// If this function is used with a locked BootMgr v1,
// then the mode-switching should be done outside this function,
// because the context-switches that are used here are not supported on BootMgr v1.
// Only works in BootLoader-mode or on unlocked bootloaders in Flash-mode!!
if (CanReadGPT())
{
throw new InvalidOperationException("Bootloader is not unlocked!");
}
byte[] Request = new byte[0x04];
const string Header = GetGPTSignature;
System.Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
byte[] Buffer = ExecuteRawMethod(Request);
if (Buffer == null || Buffer.Length < 0x4408)
{
throw new InvalidOperationException("Unable to read GPT!");
}
ushort Error = (ushort)((Buffer[6] << 8) + Buffer[7]);
if (Error > 0)
{
ThrowFlashError(Error);
//throw new NotSupportedException("ReadGPT: Error 0x" + Error.ToString("X4"));
}
byte[] GPTBuffer = new byte[Buffer.Length - 0x208];
System.Buffer.BlockCopy(Buffer, 0x208, GPTBuffer, 0, 0x4200);
return new GPT(GPTBuffer); // NOKT message header and MBR are ignored
}
internal byte[] GetGptChunk(uint Size)
{
// If this function is used with a locked BootMgr v1,
// then the mode-switching should be done outside this function,
// because the context-switches that are used here are not supported on BootMgr v1.
// Only works in BootLoader-mode or on unlocked bootloaders in Flash-mode!!
if (CanReadGPT())
{
throw new InvalidOperationException("Bootloader is not unlocked!");
}
// This function is also used to generate a dummy chunk to flash for testing.
// The dummy chunk will contain the GPT, so it can be flashed to the first sectors for testing.
byte[] GPTChunk = new byte[Size];
byte[] Request = new byte[0x04];
const string Header = "NOKT";
System.Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
byte[] Buffer = ExecuteRawMethod(Request);
if (Buffer == null || Buffer.Length < 0x4408)
{
throw new InvalidOperationException("Unable to read GPT!");
}
ushort Error = (ushort)((Buffer[6] << 8) + Buffer[7]);
if (Error > 0)
{
ThrowFlashError(Error);
//throw new NotSupportedException("ReadGPT: Error 0x" + Error.ToString("X4"));
}
System.Buffer.BlockCopy(Buffer, 8, GPTChunk, 0, 0x4400);
return GPTChunk;
}
public byte[] ReadParam(string Param)
{
byte[] Request = new byte[0x0B];
const string Header = ReadParamSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Param), 0, Request, 7, Param.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null || Response.Length < 0x10)
{
return null;
}
byte[] Result = new byte[Response[0x10]];
Buffer.BlockCopy(Response, 0x11, Result, 0, Response[0x10]);
return Result;
}
public string ReadStringParam(string Param)
{
byte[] Bytes = ReadParam(Param);
if (Bytes == null)
{
return null;
}
return System.Text.Encoding.ASCII.GetString(Bytes).Trim(['\0']);
}
public uint? ReadSecurityFlags()
{
byte[] Response = ReadParam("FCS");
if (Response == null || Response.Length != 4)
{
return null;
}
// This value is in big endian
return (uint)(Response[0] << 24 | Response[1] << 16 | Response[2] << 8 | Response[3]);
}
public uint? ReadCurrentChargeLevel()
{
byte[] Response = ReadParam("CS");
if (Response == null || Response.Length != 8)
{
return null;
}
// This value is in big endian
return (uint)(Response[0] << 24 | Response[1] << 16 | Response[2] << 8 | Response[3]) + 1;
}
public int? ReadCurrentChargeCurrent()
{
byte[] Response = ReadParam("CS");
if (Response == null || Response.Length != 8)
{
return null;
}
// This value is in big endian and needs to be XOR'd with 0xFFFFFFFF
return (int)((Response[4] << 24 | Response[5] << 16 | Response[6] << 8 | Response[7]) ^ 0xFFFFFFFF) + 1;
}
public UefiSecurityStatusResponse ReadSecurityStatus()
{
if (_SecurityStatus != null)
{
return _SecurityStatus;
}
byte[] Response = ReadParam("SS");
if (Response == null)
{
return null;
}
UefiSecurityStatusResponse Result = new()
{
IsTestDevice = Response[0],
PlatformSecureBootStatus = Convert.ToBoolean(Response[1]),
SecureFfuEfuseStatus = Convert.ToBoolean(Response[2]),
DebugStatus = Convert.ToBoolean(Response[3]),
RdcStatus = Convert.ToBoolean(Response[4]),
AuthenticationStatus = Convert.ToBoolean(Response[5]),
UefiSecureBootStatus = Convert.ToBoolean(Response[6]),
CryptoHardwareKey = Convert.ToBoolean(Response[7])
};
_SecurityStatus = Result;
return Result;
}
public FlashVersion GetFlashVersion()
{
byte[] Response = ReadParam("FAI");
if (Response == null || Response.Length < 6)
{
return null;
}
FlashVersion Result = new()
{
ProtocolMajor = Response[1],
ProtocolMinor = Response[2],
ApplicationMajor = Response[3],
ApplicationMinor = Response[4]
};
return Result;
}
internal ushort ReadSecureFfuSupportedProtocolMask()
{
return BigEndian.ToUInt16(ReadParam("SFPI"), 0);
}
public TerminalResponse GetTerminalResponse()
{
byte[] AsskMask = [1, 0, 16, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64];
byte[] Request = new byte[0xAC];
const string Header = TerminalChallengeSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Request[7] = 1;
Buffer.BlockCopy(BigEndian.GetBytes(0x18, 4), 0, Request, 0x08, 4); // Subblocktype = 0x18
Buffer.BlockCopy(BigEndian.GetBytes(0x9C, 4), 0, Request, 0x0C, 4); // Subblocklength = 0x9C
Buffer.BlockCopy(BigEndian.GetBytes(0x00, 4), 0, Request, 0x10, 4); // AsicIndex = 0x00
Buffer.BlockCopy(AsskMask, 0, Request, 0x14, 0x10);
byte[] TerminalResponse = ExecuteRawMethod(Request);
if (TerminalResponse?.Length > 0x20 && BigEndian.ToUInt32(TerminalResponse, 0x14) == TerminalResponse.Length - 0x18 && BitConverter.ToUInt32(TerminalResponse, 0x1C) == TerminalResponse.Length - 0x20)
{
// Parse Terminal Response from offset 0x18
return Terminal.Terminal.Parse(TerminalResponse, 0x18);
}
else
{
return null;
}
}
public void SendFfuHeaderV1(byte[] FfuHeader, byte Options = 0)
{
byte[] Request = new byte[FfuHeader.Length + 0x20];
const string Header = SecureFlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(BigEndian.GetBytes(0x0001, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001
Request[0x08] = 0; // Progress = 0%
Request[0x0B] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x0000000B, 4), 0, Request, 0x0C, 4); // Subblock type for header = 0x0B
Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of header + 0x0C
Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0
Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x18, 4); // Payload length = length of header
Request[0x1C] = Options; // Header options = 0
Buffer.BlockCopy(FfuHeader, 0, Request, 0x20, FfuHeader.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
throw new BadConnectionException();
}
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
public void SendFfuHeaderV2(uint TotalHeaderLength, uint OffsetForThisPart, byte[] FfuHeader, byte Options = 0)
{
byte[] Request = new byte[FfuHeader.Length + 0x3C];
const string Header = SecureFlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001
Request[0x08] = 0; // Progress = 0%
Request[0x0B] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x00000021, 4), 0, Request, 0x0C, 4); // Subblock type for header v2 = 0x21
Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x28, 4), 0, Request, 0x10, 4); // Subblock starts at 0x14, payload starts at 0x3C.
Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0
Buffer.BlockCopy(BigEndian.GetBytes(TotalHeaderLength, 4), 0, Request, 0x18, 4); // Payload length = length of header
Request[0x1C] = Options; // Header options = 0
Buffer.BlockCopy(BigEndian.GetBytes(OffsetForThisPart, 4), 0, Request, 0x1D, 4);
Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x21, 4);
Request[0x25] = 0; // No Erase
Buffer.BlockCopy(FfuHeader, 0, Request, 0x3C, FfuHeader.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
throw new BadConnectionException();
}
if (Response.Length == 4)
{
throw new WPinternalsException("Flash protocol v2 not supported", "The device reported that the Flash protocol v2 was not supported while sending the FFU header.");
}
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
public void SendFfuPayloadV1(byte[] FfuChunk, int Progress = 0, byte Options = 0)
{
byte[] Request = new byte[FfuChunk.Length + 0x1C];
const string Header = SecureFlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV1, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001
Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100)
Request[0x0B] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x0000000C, 4), 0, Request, 0x0C, 4); // Subblock type for ChunkData = 0x0C
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x08, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk
Request[0x18] = Options; // Data options = 0 (1 = verify)
Buffer.BlockCopy(FfuChunk, 0, Request, 0x1C, FfuChunk.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
throw new BadConnectionException();
}
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
public void SendFfuPayloadV2(byte[] FfuChunk, int Progress = 0, byte Options = 0)
{
byte[] Request = new byte[FfuChunk.Length + 0x20];
const string Header = SecureFlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol
Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100)
Request[0x0B] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x0000001B, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk
Request[0x18] = Options; // Data options = 0 (1 = verify)
Buffer.BlockCopy(FfuChunk, 0, Request, 0x20, FfuChunk.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
throw new BadConnectionException();
}
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
public void SendFfuPayloadV3(byte[] FfuChunk, uint WriteDescriptorIndex, uint CRC, int Progress = 0, byte Options = 0)
{
byte[] Request = new byte[FfuChunk.Length + 0x20];
const string Header = SecureFlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolAsyncV3, 2), 0, Request, 0x06, 2); // Protocol
Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100)
Request[0x0B] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x0000001D, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x2C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08
Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk
Request[0x18] = Options; // Data options = 0 (1 = verify)
Buffer.BlockCopy(BigEndian.GetBytes(WriteDescriptorIndex, 4), 0, Request, 0x19, 4); // Payload length = length of chunk
Buffer.BlockCopy(BigEndian.GetBytes(CRC, 4), 0, Request, 0x1D, 4); // Payload length = length of chunk
Buffer.BlockCopy(FfuChunk, 0, Request, 0x40, FfuChunk.Length);
byte[] Response = ExecuteRawMethod(Request);
if (Response == null)
{
throw new BadConnectionException();
}
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
public void BackupPartitionToRam(string PartitionName)
{
PartitionName = PartitionName.ToUpper();
if (new string[] { "MODEM_FSG", "MODEM_FS1", "MODEM_FS2", "SSD", "DPP" }.Any(s => s == PartitionName))
{
byte[] Request = new byte[84];
const string Header = BackupSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Request[0x07] = 1; // Subblock count = 1
Request[0x08] = 6; // Subblock ID = 6 = Create Partition Backup to RAM
Buffer.BlockCopy(BigEndian.GetBytes(73, 2), 0, Request, 0x09, 2); // Subblock length = Length of data in subblock including fillers (subblock-ID-field and subblock-length-field are not counted for the subblock-length)
System.Text.Encoding.Unicode.GetBytes(PartitionName);
byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName);
Buffer.BlockCopy(PartitionBytes, 0, Request, 0x0C, PartitionBytes.Length);
Request[0x0C + PartitionBytes.Length + 0x00] = 0; // Trailing zero
Request[0x0C + PartitionBytes.Length + 0x01] = 0;
byte[] Response = ExecuteRawMethod(Request);
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
else
{
throw new WPinternalsException("Specified partition cannot be backupped to RAM", "Partition name: \"" + PartitionName + "\".");
}
}
public void LoadMmosBinary(uint TotalLength, uint Offset, bool SkipMmosSupportCheck, byte[] MmosPart)
{
byte[] Request = new byte[MmosPart.Length + 0x20];
const string Header = LoadSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Request[0x07] = 1; // Subblock count = 1
Buffer.BlockCopy(BigEndian.GetBytes(0x0000001E, 4), 0, Request, 0x08, 4); // Subblock ID = Load MMOS Binary = 0x1E
Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length + 0x10, 4), 0, Request, 0x0C, 4); // Subblock length = Payload-length + 0x10
Buffer.BlockCopy(BigEndian.GetBytes(TotalLength, 4), 0, Request, 0x10, 4);
Buffer.BlockCopy(BigEndian.GetBytes(Offset, 4), 0, Request, 0x14, 4);
if (SkipMmosSupportCheck)
{
Request[0x18] = 1;
}
Buffer.BlockCopy(BigEndian.GetBytes(MmosPart.Length, 4), 0, Request, 0x1C, 4);
Buffer.BlockCopy(MmosPart, 0, Request, 0x20, MmosPart.Length);
byte[] Response = ExecuteRawMethod(Request);
int ResultCode = (Response[6] << 8) + Response[7];
if (ResultCode != 0)
{
ThrowFlashError(ResultCode);
}
}
internal void ErasePartition(string PartitionName)
{
// Partition "Data" can always be erased.
// Other partitions can only be erased when a valid RDC certificate is present or full SX authentication was performed.
if (PartitionName.Length > 0x23)
{
throw new ArgumentException("PartitionName cannot exceed 0x23 chars!");
}
byte[] Request = new byte[0x50];
const string Header = FlashPartitionEraseSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Request[0x06] = 1; // Protocol version must be 1
Request[0x07] = 0; // Device type = 0
byte[] PartitionBytes = System.Text.Encoding.Unicode.GetBytes(PartitionName);
Buffer.BlockCopy(PartitionBytes, 0, Request, 8, PartitionBytes.Length);
Request[0x08 + PartitionBytes.Length + 0x00] = 0; // Trailing zero
Request[0x08 + PartitionBytes.Length + 0x01] = 0;
ExecuteRawMethod(Request);
}
internal void StartAsyncFlash()
{
byte[] Request = new byte[14];
ByteOperations.WriteAsciiString(Request, 0, AsyncFlashModeSignature + "S");
Request[8] = 1; // Protocol version must be 1
Request[9] = 0; // Protocol type must be 0
ExecuteRawMethod(Request);
}
// Erases MODEM_FS1 and MODEM_FS2, and restores MODEM_FSG to MODEM_FS1
internal void DoFactoryReset()
{
byte[] Request = new byte[4];
ByteOperations.WriteAsciiString(Request, 0, FactoryResetSignature);
ExecuteRawMethod(Request);
}
internal void EndAsyncFlash()
{
byte[] Request = new byte[7];
ByteOperations.WriteAsciiString(Request, 0, AsyncFlashModeSignature + "E");
ExecuteRawMethod(Request);
}
internal void ProvisionSecureBootKeys(SecureBootKeyType KeyType) // Only for Flashmode, not BootManager mode.
{
byte[] Request = new byte[8];
ByteOperations.WriteAsciiString(Request, 0, UEFIKeysProvisionSignature);
Request[6] = 0; // Options
Request[7] = (byte)KeyType;
byte[] Response = ExecuteRawMethod(Request);
uint Status = ByteOperations.ReadUInt32(Response, 6);
if (Status != 0)
{
ThrowFlashError((int)Status);
}
}
private void ThrowFlashError(int ErrorCode)
{
string SubMessage = ErrorCode switch
{
0x0008 => "Unsupported protocol / Invalid options",
0x000F => "Invalid sub block count",
0x0010 => "Invalid sub block length",
0x0012 => "Authentication required",
0x000E => "Invalid sub block type",
0x0013 => "Failed async message",
0x1000 => "Invalid header type",
0x1001 => "FFU header contain unknown extra data",
0x0001 => "Couldn't allocate memory",
0x1106 => "Security header validation failed",
0x1105 => "Invalid hash table size",
0x1104 => "Invalid catalog size",
0x1103 => "Invalid chunk size",
0x1102 => "Unsupported algorithm",
0x1101 => "Invalid struct size",
0x1100 => "Invalid signature",
0x1202 => "Invalid struct size",
0x1203 => "Unsupported algorithm",
0x1204 => "Invalid chunk size",
0x1005 => "Data not aligned correctly",
0x0009 => "Locate protocol failed",
0x1003 => "Hash mismatch",
0x1006 => "Couldn't find hash from security header for index",
0x1004 => "Security header import missing / All FFU headers have not been imported",
0x1304 => "Invalid platform ID",
0x1307 => "Invalid write descriptor info",
0x1306 => "Invalid write descriptor info",
0x1305 => "Invalid block size",
0x1303 => "Unsupported FFU version",
0x1302 => "Unsupported struct version",
0x1301 => "Invalid update type",
0x100B => "Too much payload data, all data has already been written",
0x1008 => "Internal error",
0x1007 => "Payload data does not contain all data",
0x0004 => "Flash write failed",
0x000D => "Flash verify failed",
0x0002 => "Flash read failed",
_ => "Unknown error",
};
WPinternalsException Ex = new("Flash failed!")
{
SubMessage = "Error 0x" + ErrorCode.ToString("X4") + ": " + SubMessage
};
throw Ex;
}
public bool? ReadFuseStatus(Fuse fuse)
{
uint? flags = ReadSecurityFlags();
if (!flags.HasValue)
{
return null;
}
Fuse finalconfig = (Fuse)flags.Value;
return finalconfig.HasFlag(fuse);
}
internal bool? IsBootLoaderUnlocked()
{
UefiSecurityStatusResponse SecurityStatus = ReadSecurityStatus();
if (SecurityStatus != null)
{
return SecurityStatus.AuthenticationStatus || SecurityStatus.RdcStatus || !SecurityStatus.SecureFfuEfuseStatus;
}
return null;
}
public void FlashSectors(uint StartSector, byte[] Data, int Progress = 0)
{
// Start sector is in UInt32, so max size of eMMC is 2 TB.
byte[] Request = new byte[Data.Length + 0x40];
const string Header = FlashSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
Request[0x05] = 0; // Device type = 0
Buffer.BlockCopy(BigEndian.GetBytes(StartSector, 4), 0, Request, 0x0B, 4); // Start sector
Buffer.BlockCopy(BigEndian.GetBytes(Data.Length / 0x200, 4), 0, Request, 0x0F, 4); // Sector count
Request[0x13] = (byte)Progress; // Progress (0 - 100)
Request[0x18] = 0; // Do Verify
Request[0x19] = 0; // Is Test
Buffer.BlockCopy(Data, 0, Request, 0x40, Data.Length);
ExecuteRawMethod(Request);
}
public void Shutdown()
{
byte[] Request = new byte[4];
const string Header = ShutdownSignature;
Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length);
ExecuteRawMethod(Request);
}
internal void ResetPhone()
{
LogFile.Log("Rebooting phone", LogType.FileAndConsole);
try
{
byte[] Request = new byte[4];
ByteOperations.WriteAsciiString(Request, 0, RebootSignature);
ExecuteRawVoidMethod(Request);
}
catch (Exception ex)
{
LogFile.Log("An unexpected error happened", LogType.FileAndConsole);
LogFile.Log(ex.GetType().ToString(), LogType.FileAndConsole);
LogFile.Log(ex.Message, LogType.FileAndConsole);
LogFile.Log(ex.StackTrace, LogType.FileAndConsole);
LogFile.Log("Sending reset-request failed", LogType.FileOnly);
LogFile.Log("Assuming automatic reset already in progress", LogType.FileOnly);
}
}
internal void SwitchToPhoneInfoAppContextLegacy()
{
byte[] Request = new byte[4];
ByteOperations.WriteAsciiString(Request, 0, RebootToPhoneInfoAppSignature);
ExecuteRawVoidMethod(Request);
}
internal void WriteGPT(GPT NewGPT)
{
bool? unlocked = IsBootLoaderUnlocked();
if (unlocked == false)
{
throw new InvalidOperationException("Bootloader is not unlocked!");
}
byte[] Buffer = NewGPT.Rebuild();
uint? HeaderOffset = ByteOperations.FindAscii(Buffer, "EFI PART");
if (HeaderOffset != 0)
{
throw new BadImageFormatException();
}
FlashSectors(1, Buffer);
}
public void FlashMMOS(string MMOSPath, ProgressUpdater UpdaterPerChunk)
{
LogFile.BeginAction("FlashMMOS");
ProgressUpdater Progress = UpdaterPerChunk;
LumiaFlashAppPhoneInfo Info = ReadPhoneInfo();
if (!Info.MmosOverUsbSupported)
{
throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that loading Microsoft Manufacturing Operating System over USB is not supported.");
}
FileInfo info = new(MMOSPath);
uint length = uint.Parse(info.Length.ToString());
int offset = 0;
int maximumBufferSize = (int)Info.WriteBufferSize;
uint chunkCount = (uint)Math.Truncate((decimal)length / maximumBufferSize);
using FileStream MMOSFile = new(MMOSPath, FileMode.Open, FileAccess.Read, FileShare.Read);
for (int i = 0; i < chunkCount; i++)
{
Progress.IncreaseProgress(1);
byte[] data = new byte[maximumBufferSize];
MMOSFile.Read(data, 0, maximumBufferSize);
LoadMmosBinary(length, (uint)offset, false, data);
offset += maximumBufferSize;
}
if (length - offset != 0)
{
Progress.IncreaseProgress(1);
byte[] data = new byte[length - offset];
MMOSFile.Read(data, 0, (int)(length - offset));
LoadMmosBinary(length, (uint)offset, false, data);
}
SwitchToMmosContext();
ResetPhone();
LogFile.EndAction("FlashMMOS");
}
public void FlashFFU(string FFUPath, bool ResetAfterwards = true, byte Options = 0)
{
FlashFFU(new FFU(FFUPath), ResetAfterwards, Options);
}
public void FlashFFU(FFU FFU, bool ResetAfterwards = true, byte Options = 0)
{
FlashFFU(FFU, null, ResetAfterwards, Options);
}
public void FlashFFU(FFU FFU, ProgressUpdater UpdaterPerChunk, bool ResetAfterwards = true, byte Options = 0)
{
LogFile.BeginAction("FlashFFU");
ProgressUpdater Progress = UpdaterPerChunk;
LumiaFlashAppPhoneInfo Info = ReadPhoneInfo();
if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV1 | (ushort)FfuProtocol.ProtocolSyncV2)) == 0)
{
throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that both Protocol Sync v1 and Protocol Sync v2 are not supported for FFU flashing. Is this an old device?");
}
ulong CombinedFFUHeaderSize = FFU.HeaderSize;
byte[] FfuHeader = new byte[CombinedFFUHeaderSize];
using (FileStream FfuFile = new(FFU.Path, FileMode.Open, FileAccess.Read))
{
FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize);
SendFfuHeaderV1(FfuHeader, Options);
ulong Position = CombinedFFUHeaderSize;
byte[] Payload;
int ChunkCount = 0;
if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) == 0)
{
// Protocol v1
Payload = new byte[FFU.ChunkSize];
while (Position < (ulong)FfuFile.Length)
{
FfuFile.Read(Payload, 0, Payload.Length);
ChunkCount++;
SendFfuPayloadV1(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0);
Position += (ulong)Payload.Length;
Progress?.IncreaseProgress(1);
}
}
else
{
// Protocol v2
Payload = new byte[Info.WriteBufferSize];
while (Position < (ulong)FfuFile.Length)
{
uint PayloadSize = Info.WriteBufferSize;
if ((ulong)FfuFile.Length - Position < PayloadSize)
{
PayloadSize = (uint)((ulong)FfuFile.Length - Position);
Payload = new byte[PayloadSize];
}
FfuFile.Read(Payload, 0, (int)PayloadSize);
ChunkCount += (int)(PayloadSize / FFU.ChunkSize);
SendFfuPayloadV2(Payload, (int)((double)ChunkCount * 100 / FFU.TotalChunkCount), 0);
Position += PayloadSize;
Progress?.IncreaseProgress((ulong)(PayloadSize / FFU.ChunkSize));
}
}
}
if (ResetAfterwards)
{
ResetPhone();
}
LogFile.EndAction("FlashFFU");
}
internal void FlashRawPartition(string Path, string PartitionName)
{
FlashRawPartition(Path, null, PartitionName, null, null);
}
internal void FlashRawPartition(string Path, string PartitionName, Action<int, TimeSpan?> ProgressUpdateCallback)
{
FlashRawPartition(Path, null, PartitionName, ProgressUpdateCallback, null);
}
internal void FlashRawPartition(string Path, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(Path, null, PartitionName, null, UpdaterPerSector);
}
internal void FlashRawPartition(Stream Stream, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(null, Stream, PartitionName, null, UpdaterPerSector);
}
internal void FlashRawPartition(byte[] Buffer, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(null, new MemoryStream(Buffer, false), PartitionName, null, UpdaterPerSector);
}
internal void FlashRawPartition(GPT GPT, string Path, string PartitionName)
{
FlashRawPartition(GPT, Path, null, PartitionName, null, null);
}
internal void FlashRawPartition(GPT GPT, string Path, string PartitionName, Action<int, TimeSpan?> ProgressUpdateCallback)
{
FlashRawPartition(GPT, Path, null, PartitionName, ProgressUpdateCallback, null);
}
internal void FlashRawPartition(GPT GPT, string Path, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(GPT, Path, null, PartitionName, null, UpdaterPerSector);
}
internal void FlashRawPartition(GPT GPT, Stream Stream, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(GPT, null, Stream, PartitionName, null, UpdaterPerSector);
}
internal void FlashRawPartition(GPT GPT, byte[] Buffer, string PartitionName, ProgressUpdater UpdaterPerSector)
{
FlashRawPartition(GPT, null, new MemoryStream(Buffer, false), PartitionName, null, UpdaterPerSector);
}
private void FlashRawPartition(string Path, Stream Stream, string PartitionName, Action<int, TimeSpan?> ProgressUpdateCallback, ProgressUpdater UpdaterPerSector)
{
GPT GPT = ReadGPT();
FlashRawPartition(GPT, Path, Stream, PartitionName, ProgressUpdateCallback, UpdaterPerSector);
}
private void FlashRawPartition(GPT GPT, string Path, Stream Stream, string PartitionName, Action<int, TimeSpan?> ProgressUpdateCallback, ProgressUpdater UpdaterPerSector)
{
bool? unlocked = IsBootLoaderUnlocked();
if (unlocked == false)
{
throw new InvalidOperationException("Bootloader is not unlocked!");
}
Partition Partition = GPT.Partitions.First((p) => p.Name == PartitionName);
ulong PartitionSize = (Partition.LastSector - Partition.FirstSector + 1) * 0x200;
Stream InputStream = null;
if (Path != null)
{
InputStream = new DecompressedStream(File.Open(Path, FileMode.Open));
}
else if (Stream != null)
{
InputStream = Stream is DecompressedStream ? Stream : new DecompressedStream(Stream);
}
if (InputStream != null)
{
using (InputStream)
{
ulong? InputStreamLength = null;
try
{
InputStreamLength = (ulong)InputStream.Length;
}
catch (Exception ex)
{
LogFile.LogException(ex, LogType.FileOnly);
}
if (InputStreamLength != null && (ulong)InputStream.Length > PartitionSize)
{
throw new InvalidOperationException("Partition can not be flashed, because its size is too big!");
}
ProgressUpdater Progress = UpdaterPerSector;
if (Progress == null && ProgressUpdateCallback != null && InputStreamLength != null)
{
Progress = new ProgressUpdater((ulong)(InputStreamLength / 0x200), ProgressUpdateCallback);
}
int ProgressPercentage = 0;
const int FlashBufferSize = 0x200000; // Flash 8 GB phone -> buffersize 0x200000 = 11:45 min, buffersize 0x20000 = 12:30 min
byte[] FlashBuffer = new byte[FlashBufferSize];
int BytesRead;
ulong i = 0;
do
{
BytesRead = InputStream.Read(FlashBuffer, 0, FlashBufferSize);
byte[] FlashBufferFinalSize;
if (BytesRead > 0)
{
if (BytesRead == FlashBufferSize)
{
FlashBufferFinalSize = FlashBuffer;
}
else
{
FlashBufferFinalSize = new byte[BytesRead];
Buffer.BlockCopy(FlashBuffer, 0, FlashBufferFinalSize, 0, BytesRead);
}
FlashSectors((uint)(Partition.FirstSector + i / 0x200), FlashBufferFinalSize, ProgressPercentage);
}
if (Progress != null)
{
Progress.IncreaseProgress((ulong)FlashBuffer.Length / 0x200);
ProgressPercentage = Progress.ProgressPercentage;
}
i += FlashBufferSize;
}
while (BytesRead == FlashBufferSize);
}
}
}
}
}